I'm trying to reload the values of a table view after exiting from a seque. The process being: I perform the seque manually from the profile selection view, add a new profile name, return to the profile selection view. Then I would like to reload the table view adding the new profile name. It is running the code fine (same code as original entry into the scene), but I can't seem to get the native methods of numberOfRowsInSection and numberOfRowsInSection to repopulate the table view. I actually have to leave the screen and reenter it before the new profile name will update. Any thoughts?
//** performing seque manually
-(IBAction)buttonAddNewProfile:(id)sender
{
// creating object for profile selection screen
UIStoryboard *ProfileSelectionStoryboard=[UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
// creating object for add new profile storyboard
AddNewProfileViewController *addnewprofileVC=[ProfileSelectionStoryboard instantiateViewControllerWithIdentifier:#"Add New Profile"];
// setting the transition style
addnewprofileVC.modalTransitionStyle=UIModalTransitionStylePartialCurl;
// performing the segue
[self presentViewController:addnewprofileVC animated:YES completion:nil];
// performing new table view load on return from new profile
[self loadUsers];
}
//** function to load the new profile names in.
-(void)loadUsers
{
// retreiving the users from the database
SQLiteFunctions *sql = [[SQLiteFunctions alloc] init];
// testing for successful open
if([sql openDatabase:#"users"])
{
// setting query statement
const char *query = "SELECT * FROM users;";
// testing for that profile name existing already
if([sql getUserRecords:query] > 0)
{
// initializing array
NSMutableArray *names = [[NSMutableArray alloc] init];
// loop through object compling an array of user names
for(Users *ProfileUser in sql.returnData)
{
// adding user name to the listview array
[names addObject:ProfileUser.user_name];
}
// setting table view array to local array
tableData = names;
}
}
}
//** methods to reload the table view
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
// returning the number of rows in the table
return [tableData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
// setting up the table view cells for data population
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:#"MyCell"];
// testing for cell parameters
if (cell == nil)
{
// setting up cloned cell parameters
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MyCell"];
}
// setting cell values to the array row value
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
// returning the current row label value
return cell;
}
You have a few different options here:
1) The easiest is to simply reload the table every time that the view controller is about to display its view:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView reloadData];
}
The downside though, is that this will be executed every time that the view is displayed, even when you don't necessarily need to reload the data.
2) If you are using storyboard's and targeting iOS 6+ then you can use an unwind segue to call a specific method on your view controller when going back from the add profile view controller. For more info, see this SO question/answers: Does anyone know what the new Exit icon is used for when editing storyboards using Xcode 4.5?
3) If you are targeting older versions of iOS or aren't using storyboards, then you can create a protocol with a method that should be called whenever a new profile is added and you can reload the data whenever that method is called. There are lots of questions here on SO which cover how to do this (like dismissModalViewController AND pass data back which shows how to pass data, but you can do the same thing to just call a method).
This the actual answer from lnafzinger in the comments above. Thanks again.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView reloadData];
}
It took me a bit to figure this out, but it is because you are using
the UIModalTransitionStylePartialCurl modalTransitionStyle. I guess
they don't call it because it doesn't completely leave the screen. If
you use a different style (like the default) then it will call
viewWillAppear. If you want to stay with that, then you should
probably use one of the other methods. – lnafziger
Related
I've recently completed the apple tutorial on table views called Bird Watching and this worked fine. However, i'm now trying to take it further by adding an edit button and seem to be having a problem.
Below is the code that the MasterViewController uses to create my table. This works fine.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath (NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"BirdSightingCell";
static NSDateFormatter *formatter = nil;
if(formatter == nil){
formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterMediumStyle];
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
BirdSighting *sightingAtIndex = [self.dataController objectInListAtIndex:indexPath.row];
[[cell textLabel]setText:sightingAtIndex.name];
[[cell detailTextLabel]setText:[formatter stringFromDate:(NSDate *)sightingAtIndex.date]];
return cell;
}
I've tried using some of this code in order to get the edit button working and below is what I created. This doesn't work and I have no idea how to fix it.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
BirdSighting *sightingAtIndex = [self.dataController removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
One of the error messages I've had states "No visible #interface for BirdSightingDataController declares the selector removeObjectAtIndex.
I looked up the Bird Watching tutorial on the Apple website, to know exactly what you're talking about and which classes the objects are an instance of.
In your tableView:commitEditingStyle:forRowAtIndexPath: method, you try to remove a certain object. When removing an object, you have to make sure it's deleted from both your view (in this case your table view) and your model object (in this case your data controller). Your removed the row from your table view like this:
[tableView deleteRowsAtIndexPaths:#[indexPath]withRowAnimation:UITableViewRowAnimationFade];
This part looks good. However, you try to remove the object from your data controller using the following line of code:
BirdSighting *sightingAtIndex = [self.dataController removeObjectAtIndex:indexPath.row];
You sent the removeObjectAtIndex: method to self.dataController. The removeObjectAtIndex: method can only be sent to an instance of NSMutableArray. Your data controller is an instance of BirdSightingDataController, which is not an NSMutableArray. Thus, after trying to send the removeObjectAtIndex: method to self.dataController, you got an error.
To access the object at a certain index of your data controller array, you declared and implemented the objectInListAtIndex: method like this:
- (BirdSighting *)objectInListAtIndex:(NSUInteger)theIndex {
return [self.masterBirdSightingList objectAtIndex:theIndex];
}
Now you want to remove the object at a certain index of your data controller array, you could declare a method like removeObjectInListAtIndex: in your BirdSightingDataController header file as well. You can then implement it like this:
- (void)removeObjectInListAtIndex:(NSUInteger)theIndex {
[self.masterBirdSightingList removeObjectAtIndex:theIndex];
}
Note that this method doesn't return anything, as it just removes an object from the array. Make sure you use - (void) instead of - (BirdSighting *) in both your header and implementation file.
Now, instead of sending the removeObjectAtIndex: method to your data controller, you can simple remove the object (which the user deletes) from your data controller like this:
[self.dataController removeObjectInListAtIndex:indexPath.row];
I am trying to create a Core Data app where the user is organizing a lot of information into sections. I have a main xib that has a popup menu and a non-bordered box. Into that box, a separate xib will be loaded with the view for the section chosen from the popup button.
I decided to make a second window/panel that's a sort of accessory window. The idea is that the main window shows a summary table, while the accessory view makes it easier to input data by taking the current selection in the summary table and displaying it in text fields, graphical date pickers (instead of forcing the user to use the correct format for typing a date into the table), etc. It also holds some optional fields and displays stats, so those don't clog up my main view.
My Document.m for the main xib has:
- (id)init
{
self = [super init];
if (self) {
viewControllers = [[NSMutableArray alloc] init];
accessoryViewControllers = [[NSMutableArray alloc] init];
ManagingViewController *vc;
ManagingViewController *accessoryVC;
vc = [[SummaryViewController alloc] init];
accessoryVC = [[SummaryAccessoryViewController alloc] init];
[vc setManagedObjectContext: [self managedObjectContext]];
[accessoryVC setManagedObjectContext: [self managedObjectContext]];
[viewControllers addObject: vc];
[accessoryViewControllers addObject: accessoryVC];
}
return self;
}
And so on for the other viewControllers/xib files that will be listed in the popup button. Making a selection in the popup returns its sender tag, then calls another method that takes the tag, and loads the objectAtIndex in the vc array into the main window box and accessoryVC array into the accessory window. In the actual SummaryViewController.m I have:
- (id) init {
self = [super initWithNibName: #"SummaryView" bundle:nil];
if (self) {
[self setTitle: #"Summary"];
}
return self;
}
I built all the views, then started binding. A column in the table in the main window might be bound to arrangedObjects.aaa and the accessory view's textfield will be bound to selection.aaa, but its selection won't change when the tableview selection changes. I'm guessing that's because technically they're using two separate NSArrayControllers.
I've seen examples in books where a secondary window had data synched to the main window, and it worked because both windows came from the same xib, so they used the same NSArrayController. My question is, which of these options can I use:
1) Is there a way to make the NSArrayControllers stay in synch across multiple xib files?
2) I could move the custom view in the SummaryAccessoryView.xib into SummaryView.xib so that the one xib contains both the view for the main and accessory windows. Then they would share NSArrayControllers. But then how do I get my popup to put one view in the main window and the other in the accessory window? My current method relies on [super initWithNibName: SummaryView.xib] so I don't see any way to specify which view.
3) I guess I could cave and rebuild the whole thing to a one-window model, scrap the redundant fields and put the extra fields at the bottom part of my main view, but the user won't be able to hide it or move it around and I have that issue again with having a user formatting their dates into a tableview... It might work if I knew how to have a graphical date picker come up when the user clicks a table cell. But I'd prefer to keep the two-window model if possible.
Any ideas on how to do option 1 or 2?
EDIT: I got option 3 working:
You need a few ivars first: a date picker (myDatePicker), your table (myTable), the pop-over that houses the date picker (myPopover), and the NSArrayController (myArray). Also in my example, the date column is the first column (column 0) and I've named it in IB as "date". If you have multiple dates (like start/end dates or two tables), you can add in an NSString ("tableAndColumn") that uses #define to set flags to identify which date you need, and turn your if statement into an if-else with multiple cases.
- (BOOL) tableView:(NSTableView *)tableView
shouldEditTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row {
if (tableColumn == [myTable tableColumnWithIdentifier: #"date"]) {
//tableAndColumn = myStartDate;
[myDatePicker setDateValue: [myArray valueForKeyPath: #"selection.date"]]; //this will set your date picker to the value already in the table column
NSRect rect = [myTable frameOfCellAtColumn: 0 row: [myTable selectedRow]];
[myPopover showRelativeToRect: rect ofView: myTable preferredEdge:NSMaxYEdge];
return NO;
// } else if (tableColumn == [myTable tableColumnWithIdentifier: #"endDate"]) {
// ...
} else {
return YES;
}
}
- (void) popoverWillClose:(NSNotification *)notification {
// if ([tableAndColumn isEqualToString: MyStartDate]) {
[myArray setValue: [myDatePicker dateValue] forKeyPath: #"selection.date"];
// } else if ([tableAndColumn isEqualToString: MyEndDate]) {
// ...
// }
}
You can bind to your array controllers across NIB files by using properties of your NIB file's owner that are key-value coding and key-value observing compliant. E.g. if one of your NIB files has your NSViewController subclass as the file's owner, you can bind controls to the file's owner using key paths that start with representedObject.
In your example, you could store your view controllers (which you initialized in -[Document.m init]) in dedicated properties, and set the NSViewController's representedObject to the document instance. Then, in your NIB file, you could bind your controls to the file's owner using a key path that starts with representedObject.myViewControllerProperty.myArrayControllerProperty etc.
In my own app, I initiate a custom window controller in -[Document makeWindowControllers] using -initWithWindowNibName and store it in a mainWC property. This main window controller creates subordinate view controllers (similar to how you've done it) and sets their representedObject property to itself ([vc setRepresentedObject:self]). All bindings in other NIB files are then routed thru this main window controller via bindings to file's owner using key paths that start with representedObject.
In a similar fashion, my MainMenu.xib file connects e.g. the "Enabled" property of some menu commands to appropriate array controller properties by binding to the Application object using key paths that start with mainWindow.windowController.document.mainWC.
Hey I am working on an application that posts 50 location in a dynamic tableView and when you click on a location it will segue to a new tableView controller and posts 50 photos from that location. I created a tableViewController and then created a new file which contains all the files a tableView requires IE Cellforrowatindexpath. I have the segue connecting from the main tableViewcontroller but all the information is stored in the newfile which contains the methods that the tableViewController uses. Do I write PrepareForSegue in the tableViewController or do I write it in the file which has the methods that create the table? also if I write it in the tableViewCOntroller how do I access the cell name for one of the cells that are dynamically created? thanks.
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"Photos for location"]){
//I dont know what to use for the name
[segue.destinationViewController setPhotoInPlace: WHAT DO I CALL THIS!?
}
}
The call names come from another file which uses public API to create an array of dictionaries which have information such as name and location. The file is called flickrFetcher. Here is code that dynamically creates the cells. self.brain is an instance of flickerFetcher and topPlaces is the method called from flickrFetcher to get the NSArray of NSdictionaries.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
// Create an instance of the cell
UITableViewCell *cell;
cell = [self.tableView dequeueReusableCellWithIdentifier:#"Photo Description"];
if(!cell)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"Photo Description"];
// set properties on the cell to prepare if for displaying
//top places returns an array of NSDictionairy objects, must get object at index and then object for key
// the cellTitle has country then province, country goes in main title and province will go as subtitle.
NSString * cellTitle = [[[[self.brain class] topPlaces] objectAtIndex:self.location] objectForKey:#"_content"];
NSRange cellRange = [cellTitle rangeOfString:#","];
NSString * cellMainTitle = [cellTitle substringToIndex:cellRange.location];
cell.textLabel.text = cellMainTitle;
NSString* cellSubtitle = [cellTitle substringFromIndex:cellRange.location +2];
cell.detailTextLabel.text = cellSubtitle;
//location is an int property that allows a new selection when using objectAtIndex
self.location++;
return cell;
}
prepareForSegue: is a UIViewController method, so it needs to be in your view controller. The sender parameter should be your cell that caused the segue. You can use a table view method indexPathForCell: to get the related index path and that should be enough to find the same data you put into the cell when you implemented cellForRowAtIndexPath:.
(I'm not sure what you mean by "a new file" or what class it implements, so I can't say if that affects anything.)
I have Table View controller, then it has subclass DetailViewController, which content changes depending on cell chosen, but when move on, and from my DetailViewController go to MapView, I try to use same method I used to get text on DetailViewController, but it dont works, no matter what I do. Im stuck with it more like 3 week now:(
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.detailViewController) {
self.detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
[self.navigationController pushViewController:self.detailViewController animated:YES];
[self.detailViewController changeProductText:[teksti objectAtIndex:indexPath.row]];
[self.detailViewController changeProductText1:[adrese objectAtIndex:indexPath.row]];
[self.detailViewController changeProductText2:[laimigastunda objectAtIndex:indexPath.row]];
[self.detailViewController changeImage:[imageChange objectAtIndex:indexPath.row]];
}
}
No matter what I change here, it dont work.
It looks from the code you are showing that you are not giving a data object to your detailViewController, but rather set directly the values.
This is not the way to do it and probably the reason why you are having issues. You need to grasp the concept of MVC and go back to it.
At least you should
1. build a dictionary in first view with keys #"teksti", #"adrese", #"longitude", #latitude".
2. Create a property in DetailViewController to hold the dictionary.
3. update the values displayed when displaying the DetailViewController
So that when you press the map button, you can then push a view containing a mapView and set the map to the latitude and longitude that you have.
So it would do:
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:[teksti objectAtIndex:indexPath.row], #"teksti", [adrese objectAtIndex:indexPath.row], #"adrese", [latitude objectAtIndex:indexPath.row], #"latitude", [longitudes objectAtIndex:indexPath.row], #"longitude", nil];
[detailViewController setDict:dict];
and in the DetailViewController.m:
- (void)viewWillAppear:(BOOL)animated{
[self changeProductText:[dict objectForKey:#"teksti"]];
.... And so on
}
Your code will be useful to provide you with more details directions.
I'm starting now with Xcode on 4.2 for iOS5 and there are a few changes and I'm now crossing a problem that I can't figure out a way to solve it.
I'm doing an example with a UITablwView that is populated programmatically with 2 Sections, 1st section with only 1 Row, and 2nd Section with 3 Rows.
My aim is to select a row from the table, and based on that row, the user will be redirected to different Views.
For example:
selecting section 0 row 0, app pushes to view 1 - name setting //
selecting section 1 row 0, app pushes to view 3 - address setting
The old fashion way, this is quite simple, just needed to init a UIViewController with initWithNibName and then push the view.
Now with the storyBoard everything changes, or at least I think it changes because I can't see how to get the same result since I can't set multiple segue's from the tableView to different UIViewControllers...and to do the old fashion way I can't see where I can get the NIB names from the views on the storyBoard to init an UIViewController to push.
Does any one knows how to get to this result??
Define two "generic" segues (identified as "segue1" and "segue2", for example) in the storyboard from your source view controller, one to each destination view controller. These segues won't be associated with any action.
Then, conditionally perform the segues in your UITableViewDelegate:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Conditionally perform segues, here is an example:
if (indexPath.row == 0)
{
[self performSegueWithIdentifier:#"segue1" sender:self];
}
else
{
[self performSegueWithIdentifier:#"segue2" sender:self];
}
}
I have the same problem as you do. The problem is that you can't link your tableViewCell to multiple view controllers. However you can link your source view itself to multiple view controllers.
Control-drag the master view controller (instead of table view cell) from the scene viewer to whatever view controller you want to link. You can do this as much as you want. Notice that the segue shown in source view controller scene should be something like "Push Segue from Root View Controller ..." instead of "Push Segue from NavCell to ...".
Identify each segue link a unique name like "toDetailView1"
Finally, custom the selection in your source view controllers:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row % 2 == 1) {
[self performSegueWithIdentifier:#"toDetailView1" sender:self];
} else {
[self performSegueWithIdentifier:#"toDetailView2" sender:self];
}
}
Like #陳仁乾 and #Marco explained was completely correct. To make everything a little bit easier I would recommend you to use a single NSArray which will be initialized when viewDidLoad. Just name the segues the same as your UIViewControllers, this way you can Display a correct description of what UIViewControllers you can choose from and you can also perform the segues from this NSArray:
(Actually I'm not sure if it can cause any problems calling the segue the same as the UIViewController you want to call. Please let me know if this is BadPractise)
viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
_arraySessions = [[NSArray alloc] initWithObjects:
#"MyViewControllerName", nil];
}
cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:#"overviewCell"
forIndexPath:indexPath];
[cell.textLabel setText:_arraySessions[indexPath.row]];
return cell;
}
didSelectRowAtIndexPath
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self performSegueWithIdentifier:_arraySessions[indexPath.row]
sender:self];
}