I'm working with tableviews showing a hierarchy data structure. I take the first node and show the sons in the tableview and repeat it until the end of the tree. I'm doing it in this way:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if([[actualNode getSonAtIndex:0] sonsCount]>0) {
NSLog(#"New level");
actualNode = [actualNode getSonAtIndex:indexPath.row];
[self.tableView reloadData];
} else {
NSLog(#"Service");
[self performSegueWithIdentifier:#"Service" sender:NULL];
}
It is not the correct way but the problem is that I don't know how many levels has the tree so I can't create them in the Storyboard. The idea is create only one TableView for showing each level but doing it in that way I can't go back to the previous level on the NavBar like I could do if I was working with some controllers in the storyboard and I don't have animations. So, is there any way to do it? something like:
MyNextLevel *nextlevel = [MyNextLevel alloc];
[nextlevel setNode: actualNode]
Myactualtableview = nextlevel; (Here the next level is showed in the screen with animations and with the possibility to go back to the previous level)
Thanks.
This is why I don't like storyboards. It has always been possible to do what you want to do (if I'm understanding it correctly), but storyboards detract from it; they don't have the flexibility of doing things in code, which is what we always had to do back in iOS 3 and iOS 4 anyway.
Anyhow, you want to do something like this:
UITableViewController* tvc = [[MyTableViewController alloc] initWithNibName:#"MyNib" bundle:nil];
[self.navigationController pushViewController:tvc animated:YES];
Your business logic can just pick the class for MyTableViewController. Alternatively, MyTableViewController could be something flexible, where between those two lines you give it some configuration info that causes it to show the right data! (I have to admit, though, that you could do that same thing using a storyboard, configuring the table view controller in performSegue:.)
Related
I am rebuilding an application where I need to show a list of items. This list is retrieved using an API or retrieved from Core Data and shown in a UITableViewController. The problem I am having is that there are already seven different lists where there are small differences. Mostly of them are just different items, but also a search bar included in one list and previously stated one list will not load using an API, but from Core Data.
In my Storyboard I have added a UITableViewController with the class ItemsTableViewController which has a designed UITableViewCell. I have added an identifier to this cell so I can reuse it inside this view controller. There is a segue to this view controller from the home screen.
The idea was to create one parent object (ItemsTableViewController) and add multiple child objects (SavedItemsTableViewController, LocalItemsTableViewController, etc.) which will all use the shared logic of the parent with only some small changes (API and some custom things).
What I currently have is working, but without the child objects:
- (void)offlineButtonPressed {
[self performSegueWithIdentifier:#"openItemsTableViewController" sender:#(ItemListOffline)];
//[[self navigationController] pushViewController:[[OfflineItemsTableViewController alloc] init] animated:YES];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"openItemsTableViewController"]) {
switch((ItemList)[sender unsignedIntegerValue]) {
case ItemListOffline: { [(ItemsTableViewController *)[segue destinationViewController] retrieveOfflineDocuments]; break; }
case ItemListSearch: {
[(ItemsTableViewController *)[segue destinationViewController] retrieveDocumentsWithQuery:#""];
[(ItemsTableViewController *)[segue destinationViewController] addSearchBar];
break;
}
// Loop through all the list...
default: { NSAssert(NO, #"Unhandled type of document list."); break; }
}
}
}
So the application is calling just a function of the parent object which handles the request. But as you probably can feel, the object will be less maintainable. In comments I pushed the child view controller, but since it doesn't include the storyboard view, I need to recreate the cell from scratch, which doesn't feel good. Plus since the reuse identifier, it is not working since it is unable to dequeue a cell with the identifier set in the tableView:cellForRowAtIndexPath: method.
Now I am wondering what would be the best practice for this? It seems I can't use the save UITableViewController from the storyboard for multiple child classes, can I? But creating seven (and probably a even more in the future) controllers in the storyboard where I need to copy the cells to each controller and just give them different classes doesn't seem the way to do it. And add a method to the parent where the list would be retrieved differently and changing some stuff like adding a search bar is working, but also not the nicest way it seems.
Update: The logic in the ItemsTableViewController is pretty simple. In the delegate and datasource I handle the documents almost the same. The method that does the retrieving per list type is something like:
- (void)retrieveOfflineItems {
[self startLoading];
[[APIManager instance] getOfflineItems:^(NSArray<ItemList *> *list, NSError *error) {
[self setDocuments:list];
[[self tableView] reloadData];
}];
}
But there are more things, like the search has to add a search bar (once the view is loaded). So it needs to call multiple methods when performing the segue.
You can have a single UITableViewController, which includes all of your possible table cells, and as part of the prepareForSegue call, you should set an type identifier for the controller and the data - no matter where the data came from.
Within the UITableViewController class, you can hide / display the features that you need for this data type - such as the search bar, and in the tableView methods, choose which data source you need.
This way, the one UITableViewController class that you need to maintain is a little more complex than one dedicated class, but a lot more maintainable than 7 or more!
Assuming you have set up a variable dataType to identify the type of data you need, you could have something like this for the numberOfRowsInSection, and then similar for the other tableView methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
switch self.dataType {
case dataType.MyFirstDataType:
return myFirstDataTypeArray.count
case dataType.MySecondDataType:
return mySecondDataTypeArray.count
case dataType.TheCoreDataType:
return myCoreDataArray.count
default:
break
}
}
Create a parent UITableViewController with all subviews and cells including searchController. Initialize it and add/remove views and cells based on conditions
Still fairly new to iOS. I've managed to write a basic app to display a list/table of documents. I've included:
cell.accessoryType = UITableViewCellAccessoryDetailButton;
which display a blue i within a circle button which I guess can be used to perform some sort of action.
What I'd like to do now is, upon click, to display a subsequent screen with information about the document, buttons, add, delete functions, date, file size etc...
Is this done via segue or some other method?
Being a novice I am not sure what is/are the next step(s). If I know what steps I must take in order to get to next scree(s) I can search the net for example of how to do any given step.
Thank you for your help
The accessoryButton triggers it's own delegate method, distinct from the row selection, called:
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
Add that method to your delegate, and then do a vc transition. There are several ways to do that transition, most of the common ones are discussed here...
First, welcome to the iOS developing community!
In order to do this, I would use a segue to a detail view that you can design. When you select a row in your table using didSelectRowAtIndexPath, you would set an #property to the object in your array that was selected: self.selectedObject = self.tableviewarray objectAtIndex:indexPath.row; Then in the prepareForSegue method you can get the destination view controller and do destinationViewController.myObject = self.selectedObject; Now the detail view knows what object to display info for!
You'll need to implement the delegate method
-tableView:(UITableView*) didSelectRowAtIndexPath:(NSIndexPath*)
and handle the row tap in that callback accordingly.
An example would be like this:
-tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
YourObject *theModelForRow = [_itemList objectAtIndex:indexPath.row];
YourViewController *someNewViewController = [YourViewController viewControllerWithModel:theModelForRow];
[self.navigationController pushViewController:someNewViewController animated:YES];
}
I created a Master-Detail-Application, which uses one DetailViewController and multiple TableViewDataSources. Every time the user touches an item, i check the items class and choose the right TableSource for it.
Just like this:
if ([_detailItem isKindOfClass: [cAdress class]]) {
self.dataSource = [[AddressDetailTableSource alloc] init];
((AddressDetailTableSource *) dataSource).current = _detailItem;
} else if ([_detailItem isKindOfClass: [cActivities class]]) {
self.dataSource = [[ActivityDetailTableSource alloc] init];
((ActivityDetailTableSource *) dataSource).current = _detailItem;
}...
Sometimes i go more into Detail and push a new DetailView above the current DetailView. I do this a lot with some different views. Choosing an item in the MasterView causes, that the application goes back to the first DetailView (popToRootViewController).
I now have a problem with one view in particular. When this view is on Top and i choose an item in the MasterView, my App crashes. With NSZombies i found out, that it still tries to build the table with the wrong DataSource. Or at least it tries to call "titleForHeaderInSection" on the wrong DataSource. The error message is:
[ItemDetailTableSource tableView:titleForHeaderInSection:]:message sent to deallocated instance...
The error only occurs with this specific TableSource, also i treat same all the same.
Can anyone help me to get rid of this problem?
Any help is appreciated!
I think your app is trying to access datasource using deallocated instance, you better have individual classes for each tableView, it will simplify your work, always try to modulize the classes instead of trying to put everything in just one class.
I have a split view where the top section of my split shows some questions, and the bottom section shows some other stuff. The problem is that I had it written to "push" to a new view every time the user selects a question. This is obviously less than ideal because the user can enter a situation where they have 15 copies (more or less, depending on how many times the user selects a question) of the same question to go back through.
I thought that a simple solution would be to set a BOOL for when a user selects a question, but as it turns out, this introduces a new bug where the user can select a question once, but if they go back they are out of luck. I'm kind of stuck here, and any guidance would be greatly appreciated.
Program flow:
First you need to understand a little about what I am trying to do. I am building a historical inquiry app that focuses on allowing teachers to support student learning of historical inquiry. As such, there are core questions as well as documents the students can analyze.
Based on the way the app has come along, JSLDetailViewController displays the core questions and JSL_QuestionInteraction displays the questions for analyzing the documents.
Relevant code snippet:
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.section == 0){
if(!didSelectQuestion){
[self performSegueWithIdentifier:#"questionDisplaySegue" sender:indexPath];
didSelectQuestion = TRUE;
} else {
JSLDetailViewController *detailView = [JSLDetailViewController alloc];
detailView.telegram = indexPath.row;
[detailView setDetailItem:indexPath];
}
}else if(indexPath.section == 1){
[self performSegueWithIdentifier:#"telegramQuestionDisplaySegue" sender:indexPath];
JSL_QuestionInteraction *questionView = [[JSL_QuestionInteraction alloc] init];
questionView.managedObjectContext = self.managedObjectContext;
}
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"questionDisplaySegue"]){
JSLDetailViewController *detailView = (JSLDetailViewController *)segue.destinationViewController;
detailView.telegram = index.row;
[detailView setDetailItem:index];
JSLDetailViewController *controller = (JSLDetailViewController *)segue.destinationViewController;
controller.managedObjectContext = self.managedObjectContext;
} else if ([segue.identifier isEqualToString:#"telegramQuestionDisplaySegue"]){
JSL_QuestionInteraction *questionView = [[JSL_QuestionInteraction alloc] init];
questionView.managedObjectContext = self.managedObjectContext;
}
}
Please let me know if you need any additional details to understand this problem.
I don't know if any of what I write here will fix your problem because I still don't really understand your structure, but I see a couple of things wrong in your posted code.
First, when you're doing segues you shouldn't be alloc init'ing anything in code, the segue instantiates the new controllers for you. It's not clear what you're doing with detailView in didSelectRowAtIndexPath:, you do an alloc with no init -- you should never ever do an alloc without an init. If that detailView is something that's already present on screen, you should get a reference to that instance and set telegram and detailItem on that.
In the "if" clause of prepareForSegue you are assigning the segue.destinationViewController to two different local variables, detailView and controller -- they both point to the same thing, so there's no reason to have them both.
In the "else" clause, once again your alloc init'ing a controller, which you shouldn't do. You probably want to get the segue's destination view controller instead.
I have a UIViewController called DebugViewController that contains a UITextView, and a public method called debugPrint which is used to write an NSString into the UITextView and display it.
Is it possible to write into the UITextView before I open the UIViewController, so that when I open it, the text previously written into it is displayed?
In my parent view controllers viewDidLoad method, I'm calling initWithNibName on the DebugViewController as follows
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
I then call debugPrint as follows
[debugViewController debugPrint:#"viewDidLoad"];
And some time later I call the following to open the debugViewController
debugViewController.delegate = self;
debugViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:debugViewController animated:YES];
However all the text previously written is missing.
Please let me know how I can use a view controllers methods before the view controller displayed to the user.
Thanks,
JustinP
What you are doing is a little non-standard. The danger with that as always is that if you don't really have an expert grasp on what you're doing, you can quickly find yourself in difficulty.
If you want something set before the view is displayed to the user, then the best way to do that is to do it in the viewWillAppear method. Put it there rather than in viewDidLoad because a view might loaded once but appear many times. Where you place it depends on whether the data changes from appearance to appearance.
So, if your data is pretty static and won't change, use the viewDidLoad method.
Assuming that you'll go for the viewWillAppear option, let's do the first step by having an ivar in the view controller:
NSString *myText;
set that after init:
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
debugViewController.myText = #"My text here";
then, in debugViewController's viewWillAppear method:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
myTextView.text = myText;
}
The view controller life cycle is complex as you can see from the View Controller Programming Guide for iOS. So I'd say best not stray from the path of least resistance unless you have good reason. That said sometimes the best way to learn is by experimentation.