Using UISegmentedControl to Change the Data of Table View - ios

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.

Related

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

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

Code in viewDidLoad runs every time it is called

Hi all I am doing a course in Udemy, and the code calls for placing code in the viewDidLoad function as shown below:
override func viewDidLoad() {
super.viewDidLoad()
placesArray.append(["name":"Taj Mahal", "lat":"27.175607", "lon":"78.042112"])
}
The array append should only run once, however, when I segue to another viewController and come back, it runs the code to append again. So I now have an array with 2 rows, both of which are Taj Mahal.
I thought that the viewDidLoad function only runs code once?
Is there a way around this?
Thanks.
Addendum:
I am using Swift, so I don't see any alloc and init while creating and launching the viewController. And weird as it sounds, the video tutorial has it working in the viewDidLoad and the trainer is using the storyboard to segue from the initial table view controller to a map view on a view controller and just has a back button on the map view that segue's back to the table view controller via the storyboard as well. - Could be because I have the latest version of the Swift language and the trainer was using an earlier version, (cause I noticed some slight differences in coding earlier) but you never know. Either way whenever he touches the back button it does not run the append code anymore.
I am trying to get in contact with the trainer as some of the suggestions here, though they are good don't seem to work.
I will put the solution in here once I get in contact with the trainer.
The viewDidLoad method is called when your view controller's view finishes loading. The view will load when a view controller's view property is nil and something attempts to access it.
UIViewController *myVC = [[UIViewController alloc] init];
UIView *aView = myVC.view; // this loads myVC's view; viewDidLoad is called when it completes loading.
If the view has unloaded (usually due to memory limitations), it will be called when the view property is next accessed.
Manipulation of data sets should generally not be done within view methods. Consider moving this to the init of the view controller (or to a different "datasource" class).
I suppose you are trying to do data initialisation in viewDidLoad. If there is no other operation on placesArray before viewDidLoad, then instead of append, what about setting the placesArray directly:
placesArray = ["name":"Taj Mahal", "lat":"27.175607", "lon":"78.042112"]
Then even if your view is unloaded for some reasons. Taj Mahal will still be added once only.
viewDidLoad is called whenever the view controller's view property is set. When does this happen? It depends on how the view controller is contained:
UINavigationController
- View Controller views are loaded as they are added to the navigation stack and "unloaded" (although the viewDidUnload method is deprecated) as they are removed.
UITabBarController
- View Controller views are loaded as they are added to the tab bar regardless of whether they are on screen or not. They stay loaded as you change from tab to tab.
Depending on your needs and use case, you can create your own view controller container that does what you need. Checkout the Apple docs on the proper way to do this:
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html

iOS >> prepareForSegue >> IBOutlet Update Doesn't Work?

I'm trying to update a Label in the 2nd VC from the 1st VC within the prepareForSegue method.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
MYSecondViewController* secondVC = (MYSecondViewController*)segue.destinationViewController;
secondVC.titleLabel.text = #"First VC Says: You Are Second!!"; //This doesn't work
secondVC.dataPassString = #"First VC Says: You Are Second!!"; //This works + secondVC viewDidLoad
}
If I update the Label directly, it doesn't work.
If I update a String Property and then assign it to the Label in the Second VC viewDidLoad, it does work.
Does it mean that upon prepareForSegue call the second VC viewDidLoad method was not called yet?
Was some init method called (so the NSString object could pass)? If yes, which one?
Is there a way to update IBOutlets in the 2nd VC from the 1st VC?
The short answer is: Don't do that.
You should treat another view controller's views as private and never try to manipulate them. It breaks the OOD principle of encapsulation.
What you want to do is to add (string or other type) properties to your destination view controller, and set THOSE in prepareForSegue. Then in your destination view controller's viewWillAppear method, copy those property values into the view controllers' views.
In your case, the datePassString property is exactly what you want.
That way, if you change the structure of your second view controller down the road, and decide to display the information to a different view, you don't break the link between the 2 view controllers. Your destination view controller can still fetch the data from it's source, and do something different with it.
P.S. as the other poster said, the reason setting secondVC.titleLabel.text fails is that in prepareForSegue, the destination view controller's views haven't been loaded yet.
If you add in this line in your "prepareForSegue" method:
if(!secondVC.titleLabel)
NSLog(#"titleLabel is null and it likely hasn't been loaded yet")
You'll see that the view hasn't been loaded until it's time for it to appear (which happens after prepareForSegue). That's why the datePassString property you're using is working while the IBOutlets are null until the view is loaded.

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