I'm making an app and i have a view controller with a UISegmentedControl, and a want to switch between a MKMapView and a UITableView.
In the MKMapView i want to display a map with the users current location, and in the TableView i want to list some data. Thats it.
Sounds simple but i'm don't know how to proceed, i tried to make my view controller a tableview controller and then add the MKMapview, also tried to just add both views and a simple view controller. Anyway, there is a right or better way to do that?
Thanks guys!
You can use target-action to have the segmented control hide one view and unhide the other when it's value is changed:
- (void)segmentChanged:(id)sender
{
switch ([sender selectedSegmentIndex]) {
case 0:
{
self.tableView.hidden = NO;
self.mapView.hidden = YES;
break;
}
case 1:
{
self.tableView.hidden = YES;
self.mapView.hidden = NO;
break;
}
default:
break;
}
}
add both as subview
then whenever you want to switch just do
[self.view bringSubviewToFront:YOURVIEW];
The clean way would be to switch the subview, as soon as the button is pressed.
[view1 removeFromSuperView];
[self.view addSubview: view2];
For better performance you could save both views as a member variable, so they don't get instanciated every time.
You could even add a Viewtransition, when doing it in that way. (Eg flipping or fading)
Also in iOS5 you could write your own ViewControllerContainer. But thats way too complicated for that task.
I would use 2 navigationControllers.
Declare your first navigationController as usual, then when user tap the segmentedControl, create your tableController with another navigationController, and display it as modalViewController.
UINavigationController* modalController = [[UINavigationController alloc] initWithRootViewController:tableViewController];
[modalController setToolbarHidden:NO];
[self.navigationController presentModalViewController:modalController animated:YES];
[modalController release];
Then, when user tap the tableViewController's segmented control, just dismiss the viewController.
Related
I have a very simple question:
How can I "dismiss" a ViewController completely?
After this I want the ViewController to load like if it had been the first time I load the ViewController.
How could I do this? I already tried setNeedsDisplay() but thats not what I want.
Thanks
Jannes
I think what you mean is to remove the view (not the view controller) That way, when you enter the main view, it will run viewDidLoad again. I have done something similar in a view controller within a navigation controller. When I click the 'back' button, I wanted to remove the view and force it to load again the next time I enter it. I was able to do that with this code:
NSArray *viewsToRemove = [self.view subviews];
for (UIView *v in viewsToRemove)
{
NSLog(#"removing view %#", v);
[v removeFromSuperview];
}
I have looked around but haven't found a satisfying answer. My problem is that whenever I call popToRootViewControllerAnimated:(BOOL) it is not doing anything. When I NSLog it, it logs (null).
Let me back up a bit here. I have a table view controller that has a list of things, at the navigation bar up top there is an option to add and that takes me to a new view controller with a segue "Present as PopOver" which gets rid of the principal or main navigation bar. So I made one manually and added 2 bar button items "Cancel" and "Add". When "Cancel" is tapped, it should take the user back to the table view controller and discard changes, when "Add" button is tapped, it should also take user back to the previous table view controller with the changes. But it's not doing anything.
Here is my code.
- (IBAction)cancelButton:(UIBarButtonItem *)sender {
UINavigationController * navigationController = self.navigationController;
NSLog(#"%#", navigationController);
NSLog(#"cancel tapped though");
ListingTableViewController *rootController = [[ListingTableViewController alloc] init];
[navigationController popToRootViewControllerAnimated:NO];
[navigationController pushViewController:rootController animated:YES];
}
As far as the segue, this view controller is not connected to anything, or should I connect it? This is a noobish question indeed. Here is my xcode screenshot.
Check this link for the screenshot of the storyboard
http://i.stack.imgur.com/lqnCF.png
You must call
- (IBAction)cancelButton:(UIBarButtonItem *)sender {
NSLog(#"cancel tapped though");
[self dismissViewControllerAnimated:YES completion:nil];
}
instead of popToRootViewControllerAnimated because your VC presented and not pushed!
When presenting a view, you are not pushing it in your navigation controller, but having it presented. To dismiss it, try using [self.presentingViewController dismissViewControllerAnimated:NO completion:nil].
By default when tapping on a UITextField iOS will display a default keyboard. Is it possible to bypass this? I would like to display modally a custom view controller on tap on the textField and be able to edit the textField through this controller.
Is there a recommended way?
Following wil repalce the keyboard as the input view when the user clicks on the UItextField.
self.TextField.inputView = "your view ";
Ok tried out the exact requirement you asked for:-
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
[textField resignFirstResponder];
POCModalViewController *objPOCModalViewController = [[POCModalViewController alloc]init];
[self presentViewController:objPOCModalViewController animated:YES completion:nil];
return NO;
}
Where POCModalViewController is the controller you want to present.
I would like to post the solution i have finally implemented, which is the closest to Footyapps27 solution:
I have made the controller that will present the modal controller(which will contain internally multiple custom keyboard views) as the uitextfield delegate for any UITextField objects contained within the view of my controller.
I can now received any notification through the - (BOOL)textFieldShouldBeginEditing:(TWValueInput *)textField method when a textfield start to be edited:
Within that delegate method I have the following code snippet:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
MyCustomKeyboardVC* vc = [[UIStoryboard storyboardWithName:#"main" bundle:nil] instantiateViewControllerWithIdentifier:#"customKeyboardController"];
vc.delegate = self;
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:vc animated:NO completion:nil];
return NO;
}
returning NO within that method will prevent the default keyboard from being displayed. There is no need actually to call the resignFirstResponderon the textfield.
I should point out though that the Apple recommended way to display a custom keyboard is to provide a custom view to the textfield inputView property like Divya mentioned. Since i wanted to managed multiple keyboard view entries it was quicker for me to display a custom keyboard controller through the delegate method i mentioned above.
I have an UIWebView loaded with div, act as editor to write. Now i am adding UIWebView as sub view on UIWindow to set the frame equal to full screen and to hide the UIKeyboard, but at some button method, i need to get UIWebview from UIWindow and sent it back to UIKeyboard. Here is my code which is not working:
keyboardWindowFrame= nil;
for (UIWindow *testWindow in [[UIApplication sharedApplication] windows])
{
if (![[testWindow class] isEqual:[UIWindow class]])
{
keyboardWindowFrame = testWindow;
[webViewForEditing setFrame:CGRectMake(5, 63, 310, 400)];
[webViewForEditing.scrollView setContentSize:CGSizeMake(310, 400)];
[keyboardWindowFrame addSubview:webViewForEditing];
break;
}
}
- (IBAction)keyboardButtonSelected:(id)sender
{
[keyboardWindowFrame sendSubviewToBack:webViewForEditing]; //need to send UIWebView back to UIWindow so i can write
}
I think I am trying to do the same thing as you and so although your question is not particularly clear hopefully my answer will help.
It seems that you have a view 'webViewForEditing' which you want to add and bring in front of the keyboard. When you click a button you want to put this view behind the keyboard again.
I have also tried using the sendSubViewToBack code with no joy.
In the end though I managed to get it to work using:
[[self view] exchangeSubviewAtIndex: withSubviewAtIndex: ];
(Credit goes to Sunny with it adapted from a question here)
I used the following code below, with to switch between a UIView and the keyboard:
- (IBAction)toggleButtonPressed:(id)sender {
UIWindow * window = [UIApplication sharedApplication].windows.lastObject;
if (window.subviews.count == 1) {
[window addSubview:_menuView];
}
else {
[window exchangeSubviewAtIndex: 0 withSubviewAtIndex: 1];
}
}
I am only working with two views (_menuView and the keyboard) which means I check how many subviews my window has to make sure I only add _menuView once.
Then it is easy to exchange the two views.
Even if you had more subviews in your window I am sure you could use a modified version of this. As long as none of your other views change places then exchanging them will always switch the same two views.
NOTE: As an aside. I am not sure if this code works if called when the keyboard is not first responder. I get the variable for Window using the last object in the window, which is always the keyboard if it has been made first responder. It might need tweaking to get it working at other times.
I formulated another, more eloquent, answer while working on this:
Add the views you want to the window view that holds the keyboard view:
#interface BViewController : UIViewController {
UIWindow * window;
}
// Add the code when the keyboard is showing to ensure it is added while we have a keyboard and to make sure it is only added once
-(void) keyboardDidShow: (NSNotification *) notification {
window = [UIApplication sharedApplication].windows.lastObject;
if (window.subviews.count == 1) {
[window addSubview:_menuView];
[window addSubview:_gameView];
[window addSubview:_stickerView];
[self hideViews];
}
}
- (void)hideViews {
_menuView.hidden = YES;
_gameView.hidden = YES;
_stickerView.hidden = YES;
}
So now we have our UIWindow view which contains our views and also our keyboard. They are all hidden so our keyboard appears at the front and can be typed on.
Now use a simple function to decide which view to bring in front of the keyboard:
- (void)bringViewToFront: (UIView *)view {
[self hideViews];
view.hidden = NO;
}
Hiding the views makes sure we only have our correct view at the front.
I spent a lot of time thinking how we could move different views forward and back, using the exchange function. Actually using hide and reveal means we can get our view immediately and still easily have access to the keyboard by hiding all the views.
Reusable button bars? gets me part of the way here, but now I'm having trouble with the "back button" requirements.
I need a layout solution that:
will work on iOS 5.0 and 6.0
has a custom view at the top with several buttons; this view should be reusable across every screen (scene), as opposed to duplicating the buttons manually in Interface Builder for each scene.
has a custom "back" button in that top custom view. With the design I have to implement, I cannot just use the default navigation bar
works well with the UINavigationController; when the user taps the "back" button, the main view controller (with the button bar) should stay, but the child view controller representing the actual scene content should go back to the previous scene.
The problem currently is that the "back" button won't change the child controller--it changes the parent controller, returning to the previous scene before the scene with the button bars. I've tried this several different ways. I'm not sure if I'm not doing it right, or if it can't be done.
One possibility is to implement my own "back" functionality, keeping a stack of child view controllers and manually changing them when the user taps "back." This is awkward, however, and poor design compared to using UINavigationController.
Perhaps I am going the wrong way with this. I can't accept duplicating the button bar across every single scene in Interface Builder... but perhaps I should create it programmatically, and then I can easily call that code from each and every scene. Then I would have "normal" view controllers, and using UINavigationController would be easier. But before I go that route and completely scrap what I have so far, I wanted to see if there was another way.
Here's an overview of some parts of my solution:
I created a ButtonBarController, laying out the Storyboard with a UIView for the buttons I wanted, and a UIView for the content pane. I also layered a button with the app logo (to go to the app's main screen) on top of a back button.
Then I created a controller for each of those other screens. In those subscreens/child view controllers, I would first add a UIView at the correct size to fit in my content pane, and then would add all the other controls I wanted. I had all of those child view controllers inherit from another controller, which took care of a few common tasks--such as procuring a reference to the button bar controller, and code to help resize the views for 3.5" versus 4" screens.
I created a changeToControllerWithIndex method; I call this when the app loads, when the user clicks one of the buttons in the main button bar to change scenes, or when anything happens in a scene requiring another scene change. I overload this method to provide two additional pieces of information: providing an NSDictionary with any extra information the child view controller needs, and to tell it whether this is a top-level scene, or whether we need a back button.
(Note: it's important to set the Storyboard ID for those child view controllers in the Identity Inspector. I kept accidentally setting the Title in the Attribute Inspector instead)
- (void)changeToControllerWithIndex:(NSInteger)index {
[self changeToControllerWithIndex:index withPayload:nil isRootView:YES];
}
// This is the method that will change the active view controller and the view that is shown
- (void)changeToControllerWithIndex:(NSInteger)index withPayload:(id)payload isRootView:(BOOL)isRootView
{
if (YES) {
self.index = index;
// The code below will properly remove the the child view controller that is
// currently being shown to the user and insert the new child view controller.
UIViewController *vc = [self setupViewControllerForIndex:index withPayload:payload];
if (isRootView) {
NSLog(#"putting navigation controller in");
childNavigationController = [[UINavigationController alloc] initWithRootViewController:vc];
[childNavigationController setNavigationBarHidden:YES];
[self addChildViewController:childNavigationController];
[childNavigationController didMoveToParentViewController:self];
if (self.currentViewController){
[self.currentViewController willMoveToParentViewController:nil];
[self transitionFromViewController:self.currentViewController toViewController:childNavigationController duration:0 options:UIViewAnimationOptionTransitionNone animations:^{
[self.currentViewController.view removeFromSuperview];
} completion:^(BOOL finished) {
[self.currentViewController removeFromParentViewController];
self.currentViewController = childNavigationController;
}];
} else {
[self.currentView addSubview:childNavigationController.view];
self.currentViewController = childNavigationController;
}
[self.currentView addSubview:childNavigationController.view];
//We are at the root of the navigation path, so no back button for us
[homeButton setHidden:NO];
[backButton setHidden:YES];
} else {
//Not a root view -- we're in navigation and want a back button
[childNavigationController pushViewController:vc animated:NO];
[homeButton setHidden:YES];
[backButton setHidden:NO];
}
}
}
Then I have an overloaded method to set up each individual view controller... some require a little more preparation than others.
- (UIViewController *)setupViewControllerForIndex:(NSInteger)index {
return [self setupViewControllerForIndex:index withPayload:nil];
}
// This is where you instantiate each child controller and setup anything you need on them, like delegates and public properties.
- (UIViewController *)setupViewControllerForIndex:(NSInteger)index withPayload:(id)payload {
UIViewController *vc = nil;
if (index == CONTROLLER_HOME){
vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Home"];
} else if (index == CONTROLLER_CATEGORIES){
SAVECategoryViewController *child = [self.storyboard instantiateViewControllerWithIdentifier:#"Categories"];
if (payload) {
child.currentCategory = [(NSNumber *) [(NSDictionary *)payload objectForKey:ATTRIBUTE_CAT_ID] integerValue];
} else {
child.currentCategory = CATEGORY_ALL;
}
vc = child;
} //etc for all the other controllers...
payload = nil;
return vc;
}
I mentioned my difficulty with managing the "back" navigation. The above code ensures the navigation controllers maintain a proper "back" history, starting fresh whenever we use one of the button bar buttons to change screens. When we do use buttons inside a child controller to navigate from scene to scene, this is how we can go back:
- (IBAction)backButtonPressed:(id)sender {
[childNavigationController popViewControllerAnimated:YES];
if ([[childNavigationController viewControllers] count] <= 1) {
//Root view
[homeButton setHidden:NO];
[backButton setHidden:YES];
}
}
I think you need to implement at least one custom container view controller - the root view controller. That would be the one to host the custom button bar. Below the button bar you would add a UINavigationController the manage your other VCs. Look at this for starters:
#implementation RootVC
//...
- (void)viewDidLoad
{
self.navVC = [[UINavigationController alloc] initWithRootViewController:someOtherVC];
self.navVC.navigationBarHidden = YES;
self.navVC.view.frame = ...;
[self addChildViewController:self.navVC];
[self.view addSubview:self.navVC.view];
[self.navVC didMoveToParentViewController:self];
}
- (void)backButtonTouched:(UIButton *)button
{
[self.navVC popViewControllerAnimated:YES];
}