iPhone SDK: UIMenuController to popup above selected UITableCell - ios4

I am trying to have the UIMenuControl popup above whatever cell is selected by the user....
Currently I have this code below, which always puts the UIMenuControl in the middle of the screen on an iPhone...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[self becomeFirstResponder];
UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:#"View" action:#selector(viewListing:)];
UIMenuItem *menuItem1 = [[UIMenuItem alloc] initWithTitle:#"Edit" action:#selector(editListing:)];
UIMenuItem *menuItem2 = [[UIMenuItem alloc] initWithTitle:#"Call" action:#selector(callListing:)];
UIMenuItem *menuItem3 = [[UIMenuItem alloc] initWithTitle:#"Open Houses" action:#selector(openHouses:)];
UIMenuController *menuController = [UIMenuController sharedMenuController];
[menuController setTargetRect:CGRectMake(0, 205, 320, 200) inView:self.view];
menuController.menuItems = [NSArray arrayWithObjects:menuItem, menuItem1, menuItem2, menuItem3, nil];
menuController.arrowDirection = UIMenuControllerArrowDown;
[menuController setMenuVisible:YES animated:YES];
}
any help would great be appreciated.

It is clear that your menu is always at the center: you are hard-coding the coordinates.
Solution:
You first have to calculate the position of the cell. Then calculate your targetRect frame from that.
Hint: you can get the frame of your cell with rectForRowAtIndexPath:.

Related

How to clear previous view when loading new detail view when a row is clicked in UITableView - iOS4

I need to display a detailed view controller with multiple labels when a row in a table view is clicked. The labels I am displaying are of dynamic height based on the content, which is being read from the iPhone's internal calendar. I have been able to implement the loading of detail views based on which row in the table view has been selected. My issue is that the previous detail view does not get cleared up when a new row is clicked. Especially in the case when my previous detail view had longer labels and the new detail view has shorter labels, I can see the content of the previous labels below the new one. How can I clear up the entire Detail View controller before reloading it with the new row's data. Following is the code I am using:
RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (mdController == nil) {
MtgDetailController *aController = [[MtgDetailController alloc] initWithNibName:#"MtgDetailController" bundle:nil];
self.mdController = aController;
[aController release];
}
self.mdController.mtgIndex = indexPath.row;
// mdController.mtgIndex = indexPath.row;
// [mdController setMtgIndex:indexPath.row];
// NSInteger temp = indexPath.row;
UIActionSheet *action = [[UIActionSheet alloc]
initWithTitle:#"Select an Option"
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:#"Open Meeting", #"Dial into meeting", nil];
[action showInView:self.parentViewController.view];
[action release];
}
MtgDetailController.m (This is the Detail View Controller)
-(void) viewDidAppear:(BOOL)animated {
// [self reloadInputViews];
Smart_MeetingAppDelegate *myDelegate = (Smart_MeetingAppDelegate *)[[UIApplication sharedApplication]delegate];
CGRect scrollViewFrame = CGRectMake(0, 0, 320, 460);
NSString *title = [myDelegate.titles objectAtIndex:self.mtgIndex];
NSString *mtg_time = [myDelegate.mtg_times objectAtIndex:self.mtgIndex];
NSString *loc = #"Conf Room 123";
// detail = #"test mtg test mtg. Dial number: 3334445555, (333)444-5555, 333-333-5555. ID: 4455333344, Password: 6576567";
detail = [myDelegate.detail_array objectAtIndex:self.mtgIndex];
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:#"\\b[0-9]+(\\-)?(\\))?(\\.)?(\\s)?([0-9]+)?(\\-)?(\\.)?(\\s)?([0-9]+)?(\\-)?(\\.)?(\\s)?([0-9]+)?\\b" options:NSRegularExpressionCaseInsensitive error:nil];
NSArray* matches = [regex matchesInString:detail options:0 range:NSMakeRange(0, [detail length])];
[regex release];
unichar chr[1] = {'\n'};
NSString *singleCR = [NSString stringWithCharacters:(const unichar *)chr length:1];
NSString *titleRow = [NSString stringWithFormat:#"%# %# %#", title, singleCR, mtg_time];
NSMutableAttributedString *attrTitle = [NSMutableAttributedString attributedStringWithString:titleRow];
[attrTitle setFont:[UIFont systemFontOfSize:18] range:[titleRow rangeOfString:title]];
[attrTitle setFont:[UIFont systemFontOfSize:16] range:[titleRow rangeOfString:mtg_time]];
NSMutableAttributedString *attrLoc = [NSMutableAttributedString attributedStringWithString:loc];
NSMutableAttributedString *attrDetail = [NSMutableAttributedString attributedStringWithString:detail];
[attrDetail setFont:[UIFont systemFontOfSize:16]];
CGSize suggestedSize = [attrTitle sizeConstrainedToSize:CGSizeMake(300, FLT_MAX)];
CGRect frame1 = CGRectMake(10, 15, 300, suggestedSize.height);
OHAttributedLabel *lblTitle = [[OHAttributedLabel alloc] initWithFrame:frame1];
lblTitle.numberOfLines = 0;
CGSize size2 = [attrLoc sizeConstrainedToSize:CGSizeMake(300, FLT_MAX)];
CGRect frame2 = CGRectMake(10, suggestedSize.height+15+5, 300, size2.height);
OHAttributedLabel *lblLocation = [[OHAttributedLabel alloc] initWithFrame:frame2];
CGFloat temp = size2.height+suggestedSize.height;
CGSize size3 = [attrDetail sizeConstrainedToSize:CGSizeMake(300, FLT_MAX)];
CGRect frame3 = CGRectMake(10, temp+15+5, 300, size3.height);
OHAttributedLabel *lblDescription = [[OHAttributedLabel alloc] initWithFrame:frame3];
lblDescription.numberOfLines = 0;
lblTitle.attributedText = attrTitle;
lblLocation.attributedText = attrLoc;
lblDescription.attributedText = attrDetail;
for (NSTextCheckingResult *m in matches) {
NSString *num = [detail substringWithRange:m.range];
NSRange linkRange = [detail rangeOfString:num];
[lblDescription addCustomLink:[NSURL URLWithString:#"user://certa"] inRange:linkRange];
}
lblDescription.delegate = self;
[self.view addSubview:lblTitle];
[self.view addSubview:lblDescription];
[self.view addSubview:lblLocation];
[lblTitle release];
[lblLocation release];
[lblDescription release];
}
You add three new instances of OHAttributedLabel to your detail view every time viewDidAppear: executes. You never remove these views. You need to save references to them (lblTitle, lblDescription, and lblLocation) in your MtgDetailController, and either remove them or reuse them the next time viewDidAppear: executes.
Add three properties to your MtgDetailController:
#property (retain) UILabel *lblTitle;
#property (retain) UILabel *lblDescription;
#property (retain) UILabel *lblLocation;
(Don't forget to #synthesize them too.)
At the top of viewDidAppear:, remove the labels from their superviews:
- (void)viewDidAppear:(BOOL)animated {
[self.lblTitle removeFromSuperview];
[self.lblDescription removeFromSuperview];
[self.lblLocation removeFromSuperview];
...
At the bottom of viewDidAppear:, save your newly-created labels in the properties:
self.lblTitle = lblTitle;
self.lblLocation = lblLocation;
self.lblDescription = lblDescription;

UINavigationItem BackButtonItem not displaying

Using the below code I am implementing my own Navigation Bar. For some reason when I run my app nothing is showing up for a back (left-arrow) button on the navigation bar. However if I change the code to leftBarButtonItem, the button does appear.
// Draw Navigation Bar
UINavigationBar *navigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
[navigationBar setDelegate:self];
UINavigationItem *navigationItem = [[UINavigationItem alloc] init];
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonItemStylePlain
target:nil
action:nil];
navigationItem.backBarButtonItem = backButton;
[navigationBar pushNavigationItem:navigationItem animated:NO];
[self.view addSubview:navigationBar];
[navigationBar release];
[backButton release];
The backBarButtonItem is set by the parent ViewController.
In other words, it's not set by the ViewController on which you see it but by the ViewController to which it points. So if your ViewController is the first in line, it just won't have a back button.
Also, you are creating a NavigtionBar by yourself. This is usually not the way to go, a UINavigationBar isn't a UI-Element like a Button or a Label. You should rather use a UINavigationController to handle all the pushing and popping of your ViewControllers.
Figured it out:
// Draw Navigation Bar
UINavigationBar *navigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
[navigationBar setDelegate:self];
UINavigationItem *navigationItem = [[UINavigationItem alloc] init];
UIButton *button = [UIButton buttonWithType:101];
// add selector
[button setTitle:#"Back" forState:UIControlStateNormal];
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithCustomView:button];
navigationItem.leftBarButtonItem = backButton;
[navigationBar pushNavigationItem:navigationItem animated:NO];
[self.view addSubview:navigationBar];
[navigationBar release];
[backButton release];

UITableViewController: Data Parses but `numberOfRowsInSection:` returns NULL.

I have a UITableView populated by data parsed from xml. The parsing works but the table remains blank.
The console shows that the xml form the url is parsed and shows its components. It also shows the number of objects that the rows of tableview should have when asked in a different function but the numberOfRowsInSection: is returning Null. Therefore, the tableView in the Simulator remains blank.
Here is my code. It is simple code from a tutorial:
+++++++++++++++++ RootViewController.h++++++++++++++++++++++
#import < UIKit/UIKit.h >
#interface RootViewController : UITableViewController < NSXMLParserDelegate >{
IBOutlet UITableView *newsTable;
UIActivityIndicatorView *activityIndicator;
CGSize cellSize;
NSXMLParser *rssParser;
NSMutableArray *stories;
NSMutableDictionary *item;
NSString *currentElement;
NSMutableString *currentTitle, *currentDate, *currentSummary, *currentLink;
}
#property (nonatomic, retain) NSMutableArray *stories;
#property (nonatomic, retain) IBOutlet UITableView *newsTable;
-(void)parseXMLFileAtURL:(NSString *)URL;
#end
+++++++++++++++++++++++++++ RootViewController.m ++++++++++++++++++++++++++++++++++++++
#import "RootViewController.h"
#implementation RootViewController
#synthesize newsTable, stories;
-(void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[newsTable reloadData];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if([stories count] == 0){
NSString *path = #"http://feeds.feedburner.com/TheAppleBlog";
[self parseXMLFileAtURL:path];
}
cellSize = CGSizeMake([newsTable bounds].size.width, 60);
}
// Customize the number of sections in the table view.
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(void)parseXMLFileAtURL:(NSString *)URL {
stories = [[NSMutableArray alloc] init];
NSURL *xmlURL = [NSURL URLWithString:URL];
rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
[rssParser setDelegate:self];
[rssParser setShouldProcessNamespaces:NO];
[rssParser setShouldReportNamespacePrefixes:NO];
[rssParser setShouldResolveExternalEntities:NO];
[rssParser parse];
}
-(void)parserDidStartDocument:(NSXMLParser *)parser{
NSLog(#"Found file and started parsing");
}
-(void) parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
NSString *errorString = [NSString stringWithFormat:#"Unable to download story feed from the website (error code %i)", [parseError code]];
NSLog(#"error parsing XML: %#", errorString);
UIAlertView *errorAlert = [[UIAlertView alloc]:#"Error loading content" message:errorString delegate:self cancelButtonTitle:#"OK" otherButtonTitle:nil];
[errorAlert show];
}
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
NSLog(#"Found this Element %#", elementName);
currentElement = [elementName copy];
if ([elementName isEqualToString:#"item"]) {
item = [[NSMutableDictionary alloc] init];
currentTitle = [[NSMutableString alloc] init];
currentDate = [[NSMutableString alloc] init];
currentSummary = [[NSMutableString alloc] init];
currentLink = [[NSMutableString alloc] init];
}
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
NSLog(#"End this Element %#", elementName);
if ([elementName isEqualToString:#"item"]) {
[item setObject:currentTitle forKey:#"title"];
[item setObject:currentLink forKey:#"link"];
[item setObject:currentSummary forKey:#"summary"];
[item setObject:currentDate forKey:#"date"];
[stories addObject:[item copy]];
NSLog(#"adding Story : %#",currentTitle);
}
}
// Customize the number of rows in the table view.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"Count is = %#", [stories count]);
return [stories count];
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
NSLog(#"Found characters: %#", string);
if([currentElement isEqualToString:#"title"]){
[currentTitle appendString:string];
NSLog(#"The Title is : %#", currentTitle);
}
else if([currentElement isEqualToString:#"link"]){
[currentLink appendString:string];
NSLog(#"The Link is : %#", currentLink);
}
else if([currentElement isEqualToString:#"description"]){
[currentSummary appendString:string];
NSLog(#"The summary is : %#", currentSummary);
}
else if([currentElement isEqualToString:#"pubDate"]){
[currentDate appendString:string];
NSLog(#"The Date is : %#", currentDate);
}
}
-(void)parserDidEndDocument:(NSXMLParser *)parser{
[activityIndicator stopAnimating];
[activityIndicator removeFromSuperview];
NSLog(#"Stories array has %d items", [stories count]);
NSLog(#"Stories are : %#",stories);
}
// Customize the appearance of table view cells.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
}
// Configure the cell.
cell.textLabel.text = (NSString *)[[stories objectAtIndex:indexPath.row] objectForKey:#"title"];
return cell;
}
-(void)dealloc {
[super dealloc];
[newsTable release];
[currentDate release];
[currentElement release];
[currentSummary release];
[currentLink release];
[stories release];
[item release];
[currentTitle release];
[rssParser release];
}
You have to tell the table view that there is new data by calling reloadData after you have parsed the XML.
Is the newsTable outlet properly connected to the table view in IB? And, is the table's dataSource outlet set to your view controller?
To expand on #omz's correct answer:
The method numberOfRowsInSection is not returning NULL but zero. (In Objective-C, nil==zero and Null is a singleton object.)
The only reason that it would return zero is if the [stories count] returns zero and the only reason [stories count] would return zero is if it has no elements. Since you've confirmed that the parse works and stories has elements, then the tableview must be seeking data before the parse occurs.
This method is called first and it is the only place you reload data:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[newsTable reloadData];
// You trigger the tableview to call numberOfRowsInSection before stories is populated.
}
This method is called only after the tableview has appeared on screen and it is only after the tableview appears that you populate stories.
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if([stories count] == 0){
NSString *path = #"http://feeds.feedburner.com/TheAppleBlog";
[self parseXMLFileAtURL:path];
}
cellSize = CGSizeMake([newsTable bounds].size.width, 60);
}
However, nothing triggers the tableview to call numberOfRowsInSection again so the tableview remains blank. Simply moving the populating of stories to viewWillAppear: will fix the problem.
Each time you alter the data upon which the tableview depends (regardless of reason) you must call reloadData otherwise the tableview remains unaware that it no longer displays the current data set.
As an aside, you should use the dot notation when referring to properties to ensure they are properly retained. You should use self.stories to refer to the stories property. Otherwise, it might be released at random causing an equally random crash.

How to add a textField to a toolbar

I have a toolbar where I want to post a textField. I'm trying with the following code but it doesn't work:
UIBarButtonItem *customItem = [[UIBarButtonItem alloc] initWithTitle:#"Item" style:UIBarButtonItemStyleBordered target:self action:#selector(action:)];
UITextField *customItem1 = [[UITextField alloc] init];
NSArray *items = [NSArray arrayWithObjects: customItem, customItem1, nil];
[self setToolbarItems:items animated:YES];
The toolbar items must all be UIBarButtonItems. In order to display something else, you embed a view into the item:
UIBarButtonItem *customItem1 = [[UIBarButtonItem alloc] initWithCustomView:view];
//view is the embedded view, in your case a UITextField

How can I insert new objects at top of UITableView backed by Core Data/NSFetchedResultsController?

I have a tableview that is successfully incorporating an NSFetchedResultsController. However, I need the topmost cell in my tableview to read, "Add new object" and have UITableViewCellEditingStyleInsert instead of the default UITableViewCellEditingStyleDelete.
The FetchResultsController wants to check the managedObjectContext for objects--both to determine number of rows and to populate the table cells. The only way I can think to get around this is to create a dummy object, but I feel like there ought to be a more elegant solution.
UPDATE:
For those who might be curious as to what solution I ended up with, I decided to have my insert cell at the bottom, not the top. Here is the relevant code:
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
//self.clearsSelectionOnViewWillAppear = NO;
self.editing = YES;
self.tableView.allowsSelectionDuringEditing = YES;
self.tableView.delegate = self;
RubricAppDelegate *appDelegate = (RubricAppDelegate *)[[UIApplication sharedApplication] delegate];
managedObjectContext = [appDelegate managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"myClass" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"classID" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:nil];
NSError *error;
[fetchedResultsController performFetch:&error];
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(#"Number of sections = %d", [[fetchedResultsController sections] count]);
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
id <NSFetchedResultsSectionInfo> myClass = [[fetchedResultsController sections] objectAtIndex:section];
NSLog(#"Number of classes = %d", [myClass numberOfObjects]);
return ([[fetchedResultsController fetchedObjects] count] + 1);
}
// Customize the appearance of table view cells.
- (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] autorelease];
}
NSLog(#"FRC count + 1 = %d", ([[fetchedResultsController fetchedObjects] count] + 1));
if (indexPath.row == ([[fetchedResultsController fetchedObjects] count])) {
cell.textLabel.text = #"Add New Class";
}
else {
myClass *theClass = [fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"Class name is: %#", theClass.classTitle);
cell.textLabel.text = theClass.classTitle;
}
return cell;
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == [[fetchedResultsController fetchedObjects] count]) {
return UITableViewCellEditingStyleInsert;
}
return UITableViewCellEditingStyleDelete;
}
The result (with some junk data):
Now my only issue is getting the delete functions to work properly. You can follow my post on that issue here
Normally the add row is at the bottom.
You can accomplish this by changing the -tableView:numberOfRowsInSection: and the -tableView:cellForRowAtIndexPath: methods to adjust the cell count and adjust for it. So your -tableView:numberOfRowsInSection: would return N+1 and your -tableView:cellForRowAtIndexPath: would get object at N-1 unless N == 0 then it would return your "Add new object" cell.
There is no need to mess with the underlying Core Data elements as this is strictly a UI issue.
Update
But now I'm not sure how to return the count of my fetched objects (assuming that is what I used for "N" in your above answer). Also, wouldn't I want -tableView:cellForRowAtIndexPath to return my "Add new object" cell when the indexPath.row = (N + 1), not N = 0? I may be misunderstanding what "N" equates to, but I thought it just meant count of fetched objects.
Yes it is the count of actual objects.
You do want your -tableView:cellForRowAtIndexPath: to return a cell for your "Add new object" otherwise what is the point? You just want it to return a different type of cell.
All you are doing in this solution is adding a cell that is not part of the NSFetchedResultsController and then compensating for it when you are retrieving an actual object from the NSFetchedResultsController and when the user selects a cell.

Resources