Objective-c How To Segue from within a UITableView Sub Class - ios

Yesterday I posted this question so all of the code can be found there for the structure of my problem.
The Problem... This Time
I have come from other languages to OBJ-C and some of the OOP structures are making me cringe a little bit (I don't like packing every possible function into a single UIViewController as some seem to do). I was originally going to make a full page UITableViewController with an embedded NavigationController however the use cases of this project would not allow me to use the default navbar. So I had to put in my own navbar and use a regular UITableView (resized to be pretty much full screen) instead of the simpler option, the UITableViewController... (I am aware this all could be solved by using it, but I cannot)
Instead I have a regular UIViewController with a property containing my own custom TasksTableView.h subclass. The subclass extends UITableView as seen in the link I posted above.
The actual problem is that I cannot seem to Segue or change views from inside of this UITableView because every function which does so, seems to need to come from the UIViewController class.
I Have Tried
Calling a manual segue in the didSelectRowAtIndexPath method of my UITableView subclass.
[self performSegueWithIdentifier:#"profile" sender:sender];
Which produces an obvious error telling me that performSegueWithIdentifier does not exist on this class, which it doesn't so thats fine. Obviously it belongs to the UIViewController class that instantiated my UITableView sub class...
I have tried importing the view controller that actually renders and holds the property of my table view subclass itself and trying to push the view to the stack.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *target = [storyboard instantiateViewControllerWithIdentifier:#"SingleTaskViewController"];
AllTasksViewController *allTasksView = [[AllTasksViewController alloc] init];
if(target) {
[target setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[allTasksView presentViewController:target animated:YES completion:nil];
}
}
This gives me the error Warning: Attempt to present <SingleTaskViewController: 0x7fa15d5359f0> on <AllTasksViewController: 0x7fa15d5363c0> whose view is not in the window hierarchy!
... Even though it must be in the heirarchy because it is the view that contains and instantiated this UITableView.
I have also tried manually invoking the didSelectRowAtIndexPath from the UIViewController that holds the UITableView sub class but again it was the same kinda thing. It invoked, however obviously since I had to pass in the index, it is me picking it instead of the table telling me what was actually selected.
What I Want
I would really like to keep the UITableView sub class seperate from the UIViewController and not bring the delegate methods and protocols to the view controller. I would prefer to keep the logic separated. All I need is a way to segue or transition to the Single Task View in question and send some data with it about what was pressed.

There are good tutorials out there, but the basic idea is...
Main "ViewController" class - contains a Table View, and a "manual" Segue to a "Profile" View controller
Separate Datasource and Delegate classes for the table view
Custom Protocol / Delegate to send the "didSelectRow" action
When the main vc loads, it creates instances of the Datasource and Delegate classes, and assigns them to the table view.
It also "conforms to" a custom Protocol in the Delegate class. This allows the Delegate class to "call back" to the main vc when a row is tapped.
I put together a very simple example demonstrating this approach that can be seen here: https://github.com/DonMag/OCTableViewExample

Related

Using UISegmentedControl to Change the Data of Table View

In my app, there will be actually two table view but they will not really different from each other. The only difference between two view is the URL they have, because I update my table with the data coming from the URL. Everything other than URL remains same. I used to have 2 different view controller and switch between them, but later I thought having one Table View Controller and just change the URL and update the table with the given URL is a better idea.
I came up with this:
And below there is a part of my viewDidLoad function in table, where I take the data from URL (I'm using AFNetworking):
Problem I'm having here is, it doesn't reload the data; although I use reloadData method of the table. Shortly, I switch but nothing happens.
What am I missing do you think? Or the way I thought is wrong from the beginning?
For convenience, here is the storyboard I have, it is simple :)
You could try using the UITableViewDataSource protocol. By default a UITableViewController's dataSource is set to self (ie the UITableViewController is the data source, which is why it has all those tableView:cellForRowAtIndexPath: methods).
What you could do instead is move all those methods to your own class, MyDataSource, then on init:
self.dovizDataSource = [[MyDataSource alloc] initWithURL:<dovizURL>];
self.altinDataSource = [[MyDataSource alloc] initWithURL:<altinURL>];
When you want to switch, set
self.tableView.dataSource = self.dovizDataSource;
or
self.tableView.dataSource = self.altinDataSource;
then
self.tableView reloadData;
See UITableView docs, UITableViewDataSource protocol reference.
First problem here:
self.viewController = [self.storyboard instanciateViewControllerWithIdentifier:#"TableView"];
You instantiate manually your view controller. The storyboard already manages this allocation for you, so after your viewDidLoad, you have your TableView displayed by the system with no reference to it, and you have a reference to another TableView not displayed.
Instead, remove the line I mentioned above and catch the view controller when it's instantiated by the system, in prepareForSegue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
self.viewController = segue.destinationViewController;
}
You should name your segue by clicking on it and change the identifier in the attribute inspector, in case you have several segues in your viewController. You can get their name with segue.identifier and do actions depending on.
Second: I don't see any code that reloads your data
You may want to recall APIClient GetInformationFrom:self.URL… and in the completion block call [self.viewController.tableView reloadData] to make sure you have your new data before reloading.

How to open my UINavigationController programmatically?

One of the things I don't like about WYSIWYG/visual programming, is when you get to a point where you need to step outside of the box, you're left scratching your head. I'm at such a point right now along my iOS learning curve.
I have a custom UICollectionView thing. In two places (create and edit points), I need to present a list to the user to enable/disable (select/deselect) items from a list. So I go to the storyboard and whip up something like this:
In the past, following tutorials, I would control-drag a link from some control to the NavigationController show in the middle, I would tell it was a modal segue, tune a couple of methods, and get an arrow connecting the two for my efforts.
But in this case, I don't have obvious points to start that action from. I do have a + button. But it needs to do some other things first, and if all is well, then programmatically initiate the open, and somehow get notified of the state when it returns. Same for the individual cells, they might want to configure my table controller, and then open it, and be notified when it closes.
So I'm looking for a recipe of how one does this, what the key methods I should be looking for are. Apple's docs are great for reference, but I find figuring out how to do something from scratch difficult.
After creating a segue in your storyboard, you can initiate a segue any time programmatically by calling
[self performSegueWithIdentifier:#"segueID" sender:person];
Where "segueID" is a string you set for your segue in interface builder in the Identifier field in the identity inspector (right menu panel, 4th tab).
The segue does not need to be created from a control, you can just create one directly from one view controller to another. I usually do this on the right side menu by right-clicking on one view controller object and dragging to another one. This way, it acts as a segue that you can initiate programmatically any time you want.
As for getting notified when you come back to a view controller, (unless I'm misunderstanding your question) you can use either:
(void)viewWillAppear:(BOOL)animated
(void)viewDidAppear:(BOOL)animated
Create a UINavigationController programmatically with a desired view controller set as a root view controller. Here is an example of what you could put in a method invoked when user taps the plus button:
UIViewController *vc = [[UIStoryboard storyboardWithName:#"YourStoryboardName" bundle:nil] instantiateViewControllerWithIdentifier:#"YourViewControllerID"];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
[self presentViewController:nc
animated:YES completion:nil];
To get a state, or information about the selected items you can use Delegation and declare a protocol. Example:
#protocol YourSampleDelegate <NSObject>
- (void)didSelectItem:(NSObject *)item;
#end
Then, your view controller (the one with the plus sign) should implement this protocol:
#interface ViewController : UIViewController<YourSampleDelegate>
...
#end
#implementation ViewController
...
#pragma mark - YourSampleDelegate conformance
- (void)didSelectItem:(NSObject *)item;
{
// Do something with the item.
}
#end
You also have to create a delegate property in a view controller with collection view and set the view controller with a plus as a delegate. There are tons of examples on the Internet. I hope this shows you the right direction.

Storyboards create modal view accessible from anywhere

I need to create a modal "flow" within my app. It is made of two "scenes", these are both UITableViewController subclasses.
The user will be able to push and pop between these two table views.
At any point they will be able to press "Done" (in a nav bar) and dismiss the entire modal view to go back to where they were.
This whole modal flow needs to be accessible from several places in the app. I don't really want to create multiple modal segues to this.
My question is, creating this in a storyboard, would you create a whole new storyboard for this flow (I don't like this).
Would you just create multiple modal segues?
Should I create this flow in the same storyboard file but as a separate entity accessible by the identifier?
Or something else?
Sounds like it would be easier to use a single storyboard, but not create multiple segues everywhere. You can programmatically present the view controller pretty easily:
MyViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"MyViewController"];
// set any properties on vc here, if necessary to pass it any data
[self.window.rootViewController presentModalViewController:vc animated:YES];
You could place all this code in a helper method to reuse this code more easily, maybe a class method like this:
#interface MyViewController ...
+ (void)presentNewViewControllerModally;
...
#end
Tapping the done button:
[self.window.rootViewController dismissModalViewControllerAnimated:YES];
Note that if there's a good chance you'll never see this modal view controller, you could place that view controller in a separate xib file instead of in the storyboard, and I think that could make things more efficient (storyboard remains more lightweight). In this case, just replace the instantiteViewControllerWithIdentifier message above with:
[[MyViewController alloc] initWithNibName:#"SomeNib" bundle:nil];
...and the rest of the code is the same. I've used this technique for a "login" view controller that would only occasionally need to be presented.

Issues updating iOS ContainerView interface objects

I'm trying to update the interface contents of a ContainerView on iOS (UIViewController embedded in a UIView) from the UIViewController that it's being displayed in. However, the ContainerView just won't update its content.
The ContainerView and the ViewController are associated with different classes. I can pass data between the two View Controllers by using a few methods like these:
- (void)displayStringInContainer:(NSString *)string
The string gets successfully passed to the ContainerView from the ViewController, however when I try to display that string in an interface element - nothing happens (even though the code is getting called):
self.buttonName.titleLabel.text = string;
I've even tried calling setNeedsDisplay on the button, but nothing happens. Note that this is happening with all interface items.
Here's how I call the method on the ContainerView from my ViewController:
ContainerViewController *cvc = [[ContainerViewController alloc] init];
[cvc displayStringInContainer:#"Text"];
I've done quite a bit of searching, but haven't found anything (also tried to look on the Apple Dev Site, but it's been down for the past three days :P). Does anyone know how to update the content of a ContainerViewController from another ViewController? Why isn't this working? I've been scratching my head on this for a while now.
Alloc init'ing cvc is not the right way to get your reference -- that's a new instance, not the same instance as the one embedded in your view. You can access that instance in code from the parent controller with self.childViewControllers[0] (assuming you have only one container view). You can also get the reference by implementing prepareForSegue and use segue.destinationController (that will be your embedded controller).
What you seem to be missing in your understanding, is that the controller you get when you use a container view in the storyboard is a child view controller. It's the same as if you had called [self addChildViewController:whatever] in code and then added the child's view as a subview of your view.

how to set view controller programmatically for subview in storyboard?

(Designed in storyboard , screenshot below) I have two subviews on my rootviewcontroller's view
In my code i want to assign a separate view controller to each subview. i.e Assign a tableViewController to the TableView.
I tried to do this in awakeFromNib (or ViewDidLoad) method but to no avail. The delegate method in my tableview controller are never called. I think storyboard does the job of loading the subviews here even before the tableviewcontroller i assign can do something.
self.myTableViewController = (TodoListViewController *)[[UITableViewController alloc] initWithStyle:UITableViewStylePlain];
self.myTableView.delegate = self.myTableViewController;
self.myTableView.dataSource = self.myTableViewController;
self.myTableViewController.tableView = self.myTableView;
I am not sure if this is allowed when having views like this in storyboard or i am doing anything wrong ?
I came to this site as I had a similar problem. Actually I am doing the exact same thing: I have a viewcontroller with two subviews (all defined in a storyboard with lots of constraints).
in the containerviewcontroller in viewDidLoad I am doing the same calls as you do (but I defined the view first):
self.myTableViewController = (TodoListViewController *)[[UITableViewController alloc] initWithStyle:UITableViewStylePlain];
self.myTableViewController.tableView = self.myTableView;
self.myTableView.delegate = self.myTableViewController;
self.myTableView.dataSource = self.myTableViewController;
That works for me (I guess, here is your problem: You don't have a navigation controller around it.... and be sure that the outlets of your tableview are really connected in the storyboard.).
But the real problem comes after the correct wiring. If you tab into a cell, you probably want to give some cellID to the nextView. As you have defined your tableviewcontroller manually, only your delegate method didSelectRowAtIndexPath gets called, but not the prepareForSegue.
I have played around with instantiating the viewcontroller from the storyboard
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"aStoryboard" bundle:nil];
self.myTableViewController = [storyboard instantiateViewControllerWithIdentifier:#"myTableViewID"];
but the segue did not get called. calling it directly (performSegueWithIdentifier:...) did not work as this tableviewcontroller is not managed by a navigation controller.
For me it ended up that the prepareForSegue method gets called in the containerViewController which can delegate the calls to its subviewcontrollers.
EDIT: Actually the real solution is DON'T. There is a "Container View" object which can be added to a view and defines a region of a view controller that can include a child view controller. This is what I really wanted to do.
Try again with your viewDidLoad method, that is the simplest answer. If the method is not loading you may have to look into the other things inside you method because if the application is large as they often are using storyboards you may have conflicting methods.
I would also look at this:
http://blog.waynehartman.com/archive/2012/01/07/uistoryboard-on-ios-5-the-good-the-bad-and-the.aspx
It shows the most common accidents people make when using any storyboard function programatically
Hope that helps!
Sounds like you want to write yourself a custom container controller, e.g. similar to UISplitViewController. Here's apple's brief docs on doing this in the UIViewController class reference. You could for example instantiate the children controllers programmatically in your container controller's viewDidLoad: or viewWillAppear: methods. I don't think you can get IB to instantiate the children for you, though, in the same way you can wire up say a tab bar or navigation controller's relationships to their children. (If there is a way, I'd like to know!)
It's typically easiest to set your classes and delegates all in the storyboard (as shown in numerous totorials including this one).
If you're really trying to put a scroll view and table view into the same view, then you'll need to look into UIViewController instantiateViewControllerWithIdentifier:, you can reasonably easily pull multiple view controllers (ideally with their proper classes, delegates and sources set in the storyboard) in and add their views to your outer wrapper view. I will say that I've done this and you can do cool things with it reasonably easily, but it usually isn't the cleanest way to do things.

Resources