I have TabBarController in app, so all tab bar items are loading at same time,,
when I am update data, and coming back, then previous views are still there and new views are added above them
what should I do
my Coding is in ViewWillApear,
I haven't code in ViewDidLoad because I want to see data as I update them in database,
and ViewDidLoad is being called once, but ViewWillApear is being called everytime,
so everytime new views are added and previous are there,,,,
hope you understand what I mean,,,
You want the values displayed by those views or you want to update the views in all.
If you want to update only values, then do that in viewWillAppear: no need to create and add the whole views again. Its not good practice.
Create the views once only in loadView or viewDidLoad: method. and assign them values in viewWillAppear:
or else in your
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self removeAllSubviews];
//OR
for(UIView * view in self.view.subviews)
{
[view removeFromSuperview];
view = nil;
}
}
Related
I have a main UIViewController where most of the users' interaction happens. In the main ViewController there are three subviews. The user can load separate ViewControllers into the UIView subviews.
Each of the subviews that are loaded deal with data entry. In turn, firstResponders are called. I would like to be able to dismiss the firstRespnders through the main ViewController, maybe with a 'Done' button.
I was thinking I could add a method in each of the separate subviews with one name ex;
-(void) methodToResignResponders {}
Then, in the main ViewController call this method to the view that is currently open to the user. In turn resigning the responders that are active in the subview.
Further Information:
This is how I set up each view as a subview of the main ViewController:
UIViewController *calcVC;
//set up the view to be added depending on the name of the view that was passed
if ([viewName isEqualToString:#"Tax"]) {
calcVC= [[TAXViewController alloc]initWithNibName:#"TAXViewController" bundle:nil];
}else if ([viewName isEqualToString:#"Rent"]){
calcVC= [[RENTViewController alloc]initWithNibName:#"RENTViewController" bundle:nil];
}else //continues with more views...
//Then add it to the subview
[firstView addSubview:calcVC.view];
Not sure if I've got the gist of this, mostly because it sounds like you've already solved it yourself. :)
But, from what I can see the ViewController you are talking about is always an UIViewController instance named calcVC. If it is always this viewController's view you are referring to you can simply call [calcVC.view resignFirstResponder];
You can make a basic protocol that all of your sub-view controllers implement that has does everything you need (resign first responder and anything else).
Not sure if this answers your question but you can loop through all the subviews and call it if it exists as follows:
for (UIView *subview in [self.view subviews]) {
if ([subview respondsToSelector:#selector(resignFirstResponder)]) {
[subview resignFirstResponder];
}
}
I'm quite new to iOS development and I am stuck. Currently I am using one tab controller to switch between two view controllers (list and map view). This made it easier to use storyboard to configure the look of the two views.
Now the requirements have changed and the app needs to have one view controller with a segmented control that on click, displays either the list or the map view. In order to do this, I would need to make one view controller that can display list/map view.
I understand how the segmented controller part works, but I'm just stuck on how I can go about having two views with one or the other displayed in the same area.
How can I go about having two views in one view controller (if possible, utilizing storyboard)?
Thanks in advance!
You should not have two main views in a single view controller, instead you need to create one view controller per view that you want to show. However you can certainly have multiple subviews in a single view controller, which may be what works for you.
There are a number of approaches to solve this the problem, the correct approach would be to create a container UIViewController, and add as its childs the 2 viewcontrollers you want to show, them simply set the view to the view controller you want to display, but that would probably be overly complicated since you mention you are new to iOS development.
Therefore an easy solution (not sure if you can implement this in storyboard - since I don't like it), would be to have a single view controller, with the tabs, and 2 subviews of the main view, then you can simply switch between views by doing something like this:
[self.view addSubview:view1];
//to switch
[view1 removeFromSuperview];
[self.view addSubView:view2];
alternatively, you do not really need to remove it from superview but just hide it, and then use bringSubViewToFront to show the view that you need.
If you want to use the other approach I would recommend looking for this video the WWDC 2011 video titled "Implementing UIViewController Containment". This other question should be useful to: UISegmented control with 2 views
Hope that helps.
Using storyboard api you can switch between 2 viewControllers
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController *viewController = [self viewControllerForSegmentIndex:self.typeSegmentedControl.selectedSegmentIndex];
[self addChildViewController:viewController];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
self.currentViewController = viewController;
}
- (UIViewController *)viewControllerForSegmentIndex:(NSInteger)index {
UIViewController *viewController;
switch (index) {
case 0:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstViewController"];
break;
case 1:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
break;
}
return viewController;
}
- (IBAction)segmentChanged:(UISegmentedControl *)sender {
UIViewController *viewController = [self viewControllerForSegmentIndex:sender.selectedSegmentIndex];
[self addChildViewController:viewController];
[self transitionFromViewController:self.currentViewController toViewController:viewController duration:0.0 options:UIViewAnimationOptionTransitionNone animations:^{
[self.currentViewController.view removeFromSuperview];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
} completion:^(BOOL finished) {
[viewController didMoveToParentViewController:self];
[self.currentViewController removeFromParentViewController];
self.currentViewController = viewController ;
}];
self.navigationItem.title = viewController.title;
}
This is in reference to iOS tutorial by Raywenderlich. Hope this helps
With Storyboard it is possible in this way.
Create UIViewController with UISegmentControl and UITableView+UITableViewCell added to it.
Now you want to add MKMapView as well, hoverer, if you simply try to place the MapView on the ViewController, it will be added as new TableView cell, which is not what we want.
That's why you should not do it so. Instead, MapView has to be added to Storyboard's List of ViewControllers
Adjust the size and origin of MapView to be the same as TableView ones.
Now, setHidden to YES for either TableView of MapView, create and synthesize outlets for them. Then in Segment control Value Changed method implement switching:
- (IBAction)switchView:(id)sender {
self.theTableView.hidden = !self.theTableView.hidden;
self.theMapView.hidden = !self.theMapView.hidden;
if (!self.theTableView.hidden) {
[self.theTableView reloadData];
}
}
I have a DetailsViewController class and an ItemsViewController class. (Both derived from UITableViewController)
Selecting any of the items in the ItemsViewController brings up the DetailsViewController. In order to get it to show the new data on any but the first one, I currently have
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[self navigationItem] setTitle:title];
[[self tableView] reloadData];
}
This works, however it feels like killing a fly with a sledgehammer. What is a better way to do this?
Thanks in advance,
Alan
Combining ideas from several comments here:
Adding BOOL needReload as a member variable to the Details Controller.
Then in the details controller:
- (void)setData:(DataClass *)value {
if (value == data)
return;
id pointer = data;
data = [value retain];
[pointer release]; // release after retain
needReload = TRUE;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(needReload){
[[self navigationItem] setTitle:title];
[[self tableView] reloadData];
needReload = FALSE;
}
}
If you know that only specific rows or sections will be changing, you can direct the bake view to reload only those rows or sections. Other than that, -reloadData is the way to go for most table views.
I assume the items on the detail table changes depending on the selected item on the items table. So, yeah, this should be alright.
Other than that, you can check if the same item is selected the last time and not call reloadData during that case.
Alan,
Your statement of "In order to get it to show the new data on any but the first one" concerns me - because it tells me that you likely have a single DetailsViewController instance.
In your first table view, ItemsViewController, you probably have a didSelectRowAtIndexPath: method that you're using to push the DetailsViewController onto the UINavigationController stack.
How I solve this issue is simply creating/destroying a new DetailsViewController every time my user taps between views. So, my didSelectRowAtIndexPath: often looks like:
- (void) didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
NSInteger selectedRow = indexPath.row;
// Create a new view controller
DetailsViewController *tmpVC = [[DetailsViewController alloc] initWithNibName:#"foo" bundle:nil];
// Tell our new view controller what data it should be using
tmpVC.tableData = [self.someArrayOfData objectAtIndex:selectedRow];
// Push view controller and release it
[self.navigationController pushViewController:tmpVC animated:YES];
[tmpVC release];
}
This example assumes that you have all the data necessary for both view controllers in your ItemsViewController - that may not be the case..?
Anyway, by doing it this way, your DetailsViewController automatically loads the data. When you tap "Back" to go back to ItemsViewController, the UINavigationController would release it, destroying it. Then, when the user taps a different cell, we run this code again, creating a brand-new controller with brand-new data - so of course when it displays, it will load the data automatically - it's never displayed before.
What it sounds like you may be doing in your code is retaining the DetailsViewController as a property of the ItemsViewController class and then reusing the object. This can also work as well if you're concerned about allocations (for example, if it is a very "heavy" allocation to make a DetailsViewController), but then I think the best place to call reloadData is not inside the class itself - but rather from the didSelectRowAtIndexPath: method of ItemsViewController.
The reason I promote the creation/destruction approach as opposed to the "flyweight pattern" approach is that it keeps your code more separate - the fewer linkages between view controllers, the better. Of course, ItemsViewController will always dependo on and know about DetailsViewController, but it shouldn't necessarily have to be the other way around - and if you add the reloadData call to viewWillAppear:animated:, you're implicitly adding a non-code dependency between the two. You know that when ItemsViewController is the "parent" in the navigation stack, that's the right behavior -- but what if you suddenly started reusing that view in other part of your app that doesn't require a reload? It's a performance hit for one, and moreover, it's the kind of hidden dependency that may end up in a nasty-to-trace bug someday. So, I'd keep Details stupid and make Items contain all the complexity, if it is indeed required to only have 1 DetailsViewController (as opposed to my first idea of recreating it each time).
I would propose the reloadData and setTitle to be in the viewDidLoad and in the setter - I assume you set a property in DetailsViewController that changes the datasource of the table. So viewDidLoad reloads and sets the title, if the property has been set, the setter reloads and sets the title if isViewLoaded and the new value is different than the old one.
- (void)setSmth:(SmthClass *)value {
if (value == smth) // if they are the same and SmthClass is immutable,
// otherwise use isEqual and [self.tableView reloadData]
// before returning...
return;
id pointer = smth; // if it's a retain property
smth = [value retain];
[pointer release]; // release after retain just to be extra safe
if ([self isViewLoaded]) {
[self.tableView reloadData];
[self setTitle:title];
}
}
- (void)viewDidLoad {
if (smth) {
[self.tableView reloadData]; // maybe redundant...
[self setTitle:title];
}
}
Or you can use Key-Value observing (NSKeyValueObserving protocol) to observe your property and reloadData on notification...
I have two view controllers in a tabbar which can both edit data. Therefore, I need to call a reload_data function whenever the user makes a switch on the tabbar. How can I catch the switch or the appearance of the viewcontroller. Somehow viewDidAppear is not called on a tabbar switch. And I do not want to use the tabbarController delegate for this, because several viewControllers are affected (and I cannot set them all as delegate). What is a good way to solve this?
e.g. this didn't work:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
[self reloadData];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:YES];
[self reloadData];
}
If you're using Interface Builder, make sure the class for the viewController your expecting to reload is defined (Select the ViewController in IB, then CMD-4, make sure class is defined to be the class you want viewWillAppear and viewDidAppear to be called in).
If you're not using IB, post your code for init/calling the viewController.
I have an application that, on load, displays a UITableView with the user's data in it.
However, when the user first loads the application (before they've created any data), I'd like to display, instead of an empty table, a background image (with an arrow pointing to the 'add a record' navbar button). Once the user has added their first record, the tableview is displayed instead. I've seen numerous apps do this - the only example I can think of at present is Cha-Ching, before you have any budgets/etc set up. I can't for the life of me work out how to do this, though.
I initially added a navigationcontroller to the main window's xib, the rootViewController of which was a custom viewcontroller/xib. This rootViewController contained the background image with a hidden tableview above it, and a custom tableviewcontroller that managed the tableview. This seemed to work just fine, and if there was data it would load and display in the table. However, if I was to scroll the data offscreen, the app would crash, with this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'*** -[UITextEffectsWindow tableView:cellForRowAtIndexPath:]: unrecognized selector sent to instance 0xd2d130'
I have no clue what a UITextEffectsWindow is, or why it was trying to manage my tableview. I presume something may be hooked up incorrectly in my view hierarchy...
If there's a much simpler/more straightforward way of doing this, I'd be very grateful if someone could explain it. How would you do this?
Thanks in advance.
Here's one solution that I've been satisfied with so far.
First, I created a transparent view that was the same size as my TableView. I add this view as a sibling of the TableView whenever the TableView's data source has no data in it. I completely remove the view when data is available because transparency can affect the smoothness of the scrolling animation with TableViews.
I simply added a transparent label to this view that says something to the effect of "No Data Available". Adding a little shadowing to this label really helped to reinforce the concept that the label was 'floating' over top of the empty TableView.
I like your method of using an image though. Done properly, it might save you some work if you don't have to localize a string like I currently do.
To achieve this using a UITableViewController subclass as my only view (within a UINavigationController as per the Apple template) I used the following approach:
Create a UIView of the size of my tableView in the XIB that contains your UITableViewController and tableView.
Add an ImageView set with my placeholder image to the UIView.
Wire up the UIView as an IBOutlet (in the example code below, I called it emptyTableView)
When it is time to show the placeholder from within the UITableViewController subclass :
[self.tableView addSubView:emptyTableView];
[self.tableView setScrollEnabled:FALSE];
Disabling the scroll is necessary otherwise the user will be able to move the placeholder image up and down. Just remember to enable it once the user adds an item.
To remove the image view
[emptyTableView removeFromSuperview];
To do this, I use the following controller instead of UITableViewController. It will automatically place a view over the table when it is empty, and remove it when it is filled.
Just call [self reloadData] instead of [self.tableView reloadData] so that it can check if the table became empty.
In your subclass, implement a makeEmptyOverlayView function that will create the view to show over an empty table.
#interface MyTableViewController : UITableViewController
{
BOOL hasAppeared;
BOOL scrollWasEnabled;
UIView *emptyOverlay;
}
- (void) reloadData;
- (void) checkEmpty;
#end
#implementation MyTableViewController
- (void)viewWillAppear:(BOOL)animated
{
[self reloadData];
[super viewWillAppear: animated];
}
- (void)viewDidAppear:(BOOL)animated
{
hasAppeared = YES;
[super viewDidAppear: animated];
[self checkEmpty];
}
- (void)viewDidUnload
{
if (emptyOverlay)
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
- (void) reloadData
{
[self.tableView reloadData];
if (hasAppeared &&
[self respondsToSelector: #selector(makeEmptyOverlayView)])
[self checkEmpty];
}
- (void) checkEmpty
{
BOOL isEmpty(YES);
id<UITableViewDataSource> src(self.tableView.dataSource);
NSInteger sections(1);
if ([src respondsToSelector: #selector(numberOfSectionsInTableView:)])
sections = [src numberOfSectionsInTableView: self.tableView];
for (int i(0); i<sections; ++i)
{
NSInteger rows([src tableView: self.tableView numberOfRowsInSection: i]);
if (rows)
isEmpty = NO;
}
if (!isEmpty != !emptyOverlay)
{
if (isEmpty)
{
scrollWasEnabled = self.tableView.scrollEnabled;
self.tableView.scrollEnabled = NO;
emptyOverlay = [self makeEmptyOverlayView];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
else
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
else if (isEmpty)
{
// Make sure it is still above all siblings.
[emptyOverlay retain];
[emptyOverlay removeFromSuperview];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
}
#end
If you use Three20, you can easily set any image you want as a place holder prior to your table being populated.
So, to solve this I did as discussed in the comments above:
I created a normal UIViewController subclass, which contained a UIImageView and a UITableView. The viewController conforms to the UITableViewDelegate and UITableViewDatasource protocols, and looks after the tableView. The viewController class simply shows or hides the imageView depending on whether data is available.
I was going wrong before by trying to manage both these views with a UITableViewController. A UITableViewController has to have a tableView as its view, whereas, with this solution, a viewController can contain both the image and the tableView, and implement the necessary protocols to manage the tableView.
Thanks for all the help!