I have a UISegmentControl in my app and im trying to make it switch views like the app store. Ive tried this code with no luck:
- (IBAction)segmentSwitch:(id)sender {
UISegmentedControl *segmentedControl = (UISegmentedControl *) sender;
NSInteger selectedSegment = segmentedControl.selectedSegmentIndex;
if (selectedSegment == 0) {
//toggle the correct view to be visible
[firstView setHidden:NO];
[secondView setHidden:YES];
}
else{
//toggle the correct view to be visible
[firstView setHidden:YES];
[secondView setHidden:NO];
}
}
Does anybody know how I could switch views? Any help is appreciated. Thanks
That code will work as long as both views are currently subviews of a visible parent view (or window).
Also, you can simplify your IBAction a bit like this:
- (IBAction)segmentSwitch:(UISegmentedControl*)segmentedControl {
//UISegmentedControl *segmentedControl = (UISegmentedControl *) sender;
NSInteger selectedSegment = segmentedControl.selectedSegmentIndex;
It would help to know what does happen when this code is executed and what the starting point is-- is firstView already visible, and is there any effect at all when the code runs?
If firstView is visible but the code never hides it, I suspect that the "firstView" variable is not actually connected to the view. You're telling firstView to hide, so if the view never hides, "firstView" is probably nil. Set a breakpoint in this method and check both firstView and secondView to make sure they have references to the views you want to manipulate.
Related
So i wanted to build a tabbar that has more than 5 items and is scrollable and found this article.
Easy done by subclassing the UITabBarController and hide the existing tabbar:
for(UIView *view in self.view.subviews)
{
if([view isKindOfClass:[UITabBar class]])
{
view.hidden = YES;
break;
}
}
Then i just add a UIScrollView and throw some buttons in there based on the items-collection of the tabbarcontroller.
int i = 0;
for (UITabBarItem *item in self.tabBar.items)
{
UIView *tab = [[[UIView alloc] initWithFrame:CGRectMake(i*60, 0, 60, 60)] autorelease];
UIButton *btn = [[[UIButton alloc] initWithFrame:CGRectMake(7.5, 1, 45, 45)] autorelease];
[btn setImage:item.image forState:UIControlStateNormal];
btn.tag = i;
[btn addTarget:self action:#selector(didSelectTabrBarItem:) forControlEvents:UIControlEventTouchUpInside];
[tab addSubview:btn];
[self.scrollView addSubview:tab];
i++;
if(self.selectedViewController == nil)
[self setSelectedIndex:0];
}
I am overriding the setSelectedindex/ViewController since i need some addition drawing.
-(void)setSelectedViewController:(UIViewController *)selectedViewController
{
[super setSelectedViewController:selectedViewController];
[self startTimer];
}
-(void)setSelectedIndex:(NSUInteger)selectedIndex
{
[super setSelectedIndex:selectedIndex];
[self startTimer];
}
The problem is that when I am pressing button number 5, 6 or 7, the tabbarcontroller opens the More view. How do i get rid of that and make the last three items act like the other ones? - Could it be the call to the super?
My guess would be to completely kill the UITabBarController and implement my own custom tabbar. But is it possible to disable the more menu and have the UITabBarController select item 5, 6 and 7 as normal?
So, since I'm too lousy to write a completely new tab bar i decided to investigate and try to hack UITabBarController.
And here's the solution:
The actual problem is that when you touch a tab bar item with index above 4, the UITabBarController vil actually display the moreNavigationController. This is a UINavigationController containing a view of type UIMoreViewControllerList, which is a type from the private Cocoa framework together with an instance of the ViewController you selected.
So how do we get rid of the More button?
Simply remove the UIMoreViewControllerList from the moreNavigationController collection, leaving only the ViewController you selected.
-(void)setSelectedViewController:(UIViewController *)selectedViewController
{
[super setSelectedViewController:selectedViewController];
if([self.moreNavigationController.viewControllers count] > 1)
{
//Modify the view stack to remove the More view
self.moreNavigationController.viewControllers = [[[NSArray alloc] initWithObjects:self.moreNavigationController.visibleViewController, nil] autorelease];
}
}
Well that leaves us with a Edit button in the top right corner (Titlebar).
How do you get rid of that, then?
Yeah. Thats another dirty hack. To remove the Edit button I'd actually have to implement one method from the UINavigationControllerDelegate for the moreNavigationController on my custom UITabBarController.
//navigationController delegate
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([navigationController isKindOfClass:NSClassFromString(#"UIMoreNavigationController")])
{
// We don't need Edit button in More screen.
UINavigationBar *morenavbar = navigationController.navigationBar;
UINavigationItem *morenavitem = morenavbar.topItem;
morenavitem.rightBarButtonItem = nil;
}
}
And thats the way to kill off the default More functionality. I really think Apple pissed their own pants here creating a UITabBarController that both handles logic and UI stuff.
Why not create a controller that has the logic to preload the ViewControllers and switch between then, and then an implementation you can use if you want the More thingie. - Or even better: Make it possible to disable the More functionality.
This is pretty close, it may help you
https://github.com/iosdeveloper/InfiniTabBar
I should finalize this thread. I have been having the stuff above in a App Store for a year and it has caused massive problems in the long run. It works, but it quirks when you rely on the built in features of UITabbarcontroller as it messes around with the view stack.
After going around this hot ash for over a year we decided to build our own tabbarcontroller/menucontroller. Thta took like a day and have freed us from all the fixes and quirks.
My hack works, but I recommend building your own navigation class - it will pay off in the long run :-)
I had a similar problem and solved it by using a UITabBarController and hiding the tab bar. Then I drew a custom tab bar over the top of it and when a button for a tab was clicked, call
tabbar.selectedIndex = index
Where 'tabbar' is the original UITabBarController. Setting the selectedIndex property of a UITabBarController changes the currently displayed view controller to the controller at that index. This way you still get all the functionality of a UITabBarController but you can have as many tabs as you want and customize it however you want.
Otherwise, I don't think there is a way to remove the "more" functionality.
Here is the setup of my iPhone App:
I have a UITabBarController that has 4 View Controllers (1 UINavigationController & 3 UIViewControllers).
Onload my app is defaulted to the UINavigationController, where there is a grouped UITableView which gives two navigation options, when the user hits the first option the relevant UITableViewController (Contains a list of locations) is pushed onto the stack.
What I would like to happen on screen is to have a UISegmentedControl that has two options, a "List View" (which the user sees by default when the UIViewController gets pushed) ans a "Map View" that will allow the relevant locations to be represented on the map.
I use the follwing code to create the UISegmentedControl on the NavigationBar:
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:segmentItems];
[segmentedControl setSelectedSegmentIndex:0];
[segmentedControl setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[segmentedControl setSegmentedControlStyle:UISegmentedControlStyleBar];
[segmentedControl addTarget:self action:#selector(segmentAction:) forControlEvents:UIControlEventValueChanged];
[[self navigationItem] setTitleView:segmentedControl];
[segmentedControl release];
And this is the method that the UISegmented control will call when changed:
- (void) segmentAction:(id)sender
{
UISegmentedControl *segmentControl = sender;
if([segmentControl selectedSegmentIndex] == 0)
{
NSLog(#"LIST VIEW CHOSEN!");
}
else
{
NSLog(#"MAP VIEW CHOSEN!");
}
}
So basically my question is what is the correct/best way to implement what I am trying to achieve, I dont want to push the controller onto the navigation stack, I just want to toggle it in place (possibly with animation). All interfaces are built in IB.
One way i tried to do it, that worked but didn't feel like it was the correct was by creating two separate UIViews in my nib file (One for the List View and one one for the Map view), and then using setView: appropriately.. but then I thought shouldn't each of these views have there own controller and/or own nib?
- (void) segmentAction:(id)sender
{
UISegmentedControl *segmentControl = sender;
if([segmentControl selectedSegmentIndex] == 0)
{
NSLog(#"LIST VIEW CHOSEN!");
[self setView:listView]; //Declared as an UIView IBOutlet
}
else
{
NSLog(#"MAP VIEW CHOSEN!");
[self setView:mapView]; //Declared as an UIView IBOutlet
}
}
Some semi related examples I have come across use "removeFromSuperview" and "addSubview" and this has somewhat confued me, i'm relatively new to Cocoa Touch development, so any help getting my head around this would be appreciated.
Thanks
There are a couple of ways to handle this, if you are simply going between a TableView (that is already on the UINavigationController stack) and a mapview that goes with this SPECIFIC UITableView, you could have the segmentedControl display the mapviewController modally, that is presentModalViewController:. This does have some drawbacks as to functionality, but it could be what you are looking for if the mapview will be relatively simple.
Another way of implementing this, and probably the most robust way, is to have one view controller that handles both the table view and the map view. This is ideal if they have the same data source for certain.
One other way is to have two view controllers, one each for the table view and map view, and literally do pushes and pops on the navigation controller stack in your segmentAction method.
Ex:
- (void) segmentAction:(id)sender
{
UISegmentedControl *segmentControl = sender;
if([segmentControl selectedSegmentIndex] == 0)
{
NSLog(#"LIST VIEW CHOSEN!");
[self.navigationController popViewControllerAnimated:YES];
[self.navigationController pushViewController:listViewController animated:YES];
}
else
{
NSLog(#"MAP VIEW CHOSEN!");
[self.navigationController popViewControllerAnimated:YES];
[self.navigationController pushViewController:mapViewController animated:YES];
}
}
I would make sure to read the Human Interface Guidelines, as well as the documentation of how view controllers and navigation controllers work.
I came across this while looking for a solution to the same problem. Seems to work pretty well for me.
switch (self.menu.selectedSegmentIndex)
{
case 0 :
// setting text to name label
boynamelabel.text=[boysnamearray objectAtIndex:0];
girlnamelabel.text=[girlsnamearray objectAtIndex:0];
// setting text to percent label
boyspercentlabel.text=[boyspercentarray objectAtIndex:0];
girlspercentlabel.text=[girlsprecentarray objectAtIndex:0];
// setting image to image view
boysimage.image=[UIImage imageNamed:#"boy1.jpeg"];
girlsimage.image=[UIImage imageNamed:#"girl1.jpeg"];
break;
case 1:
// setting text to name label
boynamelabel.text=[boysnamearray objectAtIndex:1];
girlnamelabel.text=[girlsnamearray objectAtIndex:1];
// setting text to percent label
boyspercentlabel.text=[boyspercentarray objectAtIndex:1];
girlspercentlabel.text=[girlsprecentarray objectAtIndex:1];
// setting image to image view
boysimage.image=[UIImage imageNamed:#"boy2.jpeg"];
girlsimage.image=[UIImage imageNamed:#"girl2.jpeg"];
break;
case 2 :
// setting text to name label
boynamelabel.text=[boysnamearray objectAtIndex:2];
girlnamelabel.text=[girlsnamearray objectAtIndex:2];
// setting text to percent label
boyspercentlabel.text=[boyspercentarray objectAtIndex:2];
girlspercentlabel.text=[girlsprecentarray objectAtIndex:2];
// setting image to image view
boysimage.image=[UIImage imageNamed:#"boy3.jpeg"];
girlsimage.image=[UIImage imageNamed:#"girl3.jpeg"];
break;
default:
break;
}
I have a core data application which uses a navigation controller to drill down to a detail view and then if you edit one of the rows of data in the detail view you get taken to an Edit View for the that single line, like in Apples CoreDataBooks example (except CoreDataBooks only uses a UITextField on its own, not one which is a subview of UITableViewCell like mine)!
The edit view is a UITableviewController which creates its table with a single section single row and a UITextfield in the cell, programatically.
What I want to happen is when you select a row to edit and the edit view is pushed onto the nav stack and the edit view is animated moving across the screen, I want the textfield to be selected as firstResponder so that the keyboard is already showing as the view moves across the screen to take position. Like in the Contacts app or in the CoreDataBooks App.
I currently have the following code in my app which causes the view to load and then you see the keyboard appear (which isn't what I want, I want the keyboard to already be there)
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[theTextField becomeFirstResponder];
}
You can't put this in -viewWillAppear as the textfield hasn't been created yet so theTextField is nil. In the CoreDataBooks App where they achieve what i want they load their view from a nib so they use the same code but in -viewWillAppear as the textfield has already been created!
Is there anyway of getting around this without creating a nib, I want to keep the implementation programatic to enable greater flexibility.
Many Thanks
After speaking with the Apple Dev Support Team, I have an answer!
What you need to do is to create an offscreen UITextField in -(void)loadView; and then set it as first responder then on the viewDidLoad method you can set the UITextField in the UITableViewCell to be first responder. Heres some example code (remember I'm doing this in a UITableViewController so I am creating the tableview as well!
- (void)loadView
{
[super loadView];
//Set the view up.
UIView *theView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = theView;
[theView release];
//Create an negatively sized or offscreen textfield
UITextField *hiddenField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, -10, -10)];
hiddenTextField = hiddenField;
[self.view addSubview:hiddenTextField];
[hiddenField release];
//Create the tableview
UITableView *theTableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds] style:UITableViewStyleGrouped];
theTableView.delegate = self;
theTableView.dataSource = self;
[self.view addSubview:theTableView];
[theTableView release];
//Set the hiddenTextField to become first responder
[hiddenTextField becomeFirstResponder];
//Background for a grouped tableview
self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//Now the the UITableViewCells UITextField has loaded you can set that as first responder
[theTextField becomeFirstResponder];
}
I hope this helps anyone stuck in the same position as me!
If anyone else can see a better way to do this please say.
Try do it in viewDidAppear method, works for me.
I think the obvious solution is to create the textfield in the init method of the view controller. That is usually where you configure the view because a view controller does require a populated view property.
Then you can set the textfield as first responder in viewWillAppear and the keyboard should be visible as the view slides in.
have you tried using the uinavigationcontroller delegate methods?:
navigationController:willShowViewController:animated:
When my iPhone app starts up, the main screen has a keyboard. Currently the keyboard rises as soon as the rest of the interface is displayed and this is visually distracting.
How can I have the view display with the keyboard already up?
Since I am already faking some of the rest of the screen during startup so that the user sees what they last were doing, I thought that I could fake the keyboard as well. But if the motion is there when the real keyboard appears, I've lost the effect. The keyboard is, as far as I know, on a separate window, not just a separate view, so I can't overlay it with my own image.
Is there a way to either overlay the keyboard withy my own image as it appears, or not show the keyboard until it is all the way up, or make its animation instant?
My original answer has the keyboard animate in along with the view controller if it's an animated transition (i.e. pushing a view controller or presenting a modal controller with animated: YES). However, the keyboard still animates in if the new view controller is displayed without an animated transition, so it doesn't solve your problem.
Here's another approach that worked in my testing. Try disabling animations while you're displaying the controller + keyboard.
[UIWindow beginAnimations: nil context: NULL];
[UIWindow setAnimationsEnabled: NO];
RestoredController *controller = [[[RestoredController alloc] init] autorelease];
[self.navigationController pushViewController: controller animated: NO];
[UIWindow commitAnimations];
You'll still need to make field the first responder in viewWillAppear: or viewDidAppear:
In viewWillAppear:, ensure the view is loaded (via self.view) and set the first responder to the correct field. This will display the keyboard fully when the view is actually displayed instead of animating it in.
For example:
- (void) viewWillAppear: (BOOL) animated {
[super viewWillAppear:animated];
NSString *storedID = [[NSUserDefaults standardUserDefaults] stringForKey:#"storedID"];
if ([storedID length] > 0) {
idField.text = storedEmail;
[passwordField becomeFirstResponder];
} else {
[idField becomeFirstResponder];
}
}
Not an answer, but here is some code.
I set the font for my UITextView in viewDidLoad. The view is instantiated by the NIB and the outlet is correctly set up.
-(void)viewWillAppear:(BOOL)animated;
{
[super viewWillAppear:animated];
// Start with the text from the currently edited message
NSString *startString = self.messageManager.editingMessage.text;
// start string processing omitted
self.editingMessageEditingView.text = startString;
if([self showingMessageList]) {
[self.editingMessageEditingView resignFirstResponder];
} else {
#if DEFAULT_SCREEN==1
[[self.tools.items objectAtIndex:kPVToolBarItemDelete] setEnabled:NO];
[[self.tools.items objectAtIndex:kPVToolBarItemSendLater] setEnabled:NO];
#else
[self.editingMessageEditingView becomeFirstResponder];
#endif
}
Does anyone know how to cancel (resign First Responder) out of a UISearchBar when you tap below the search text box and above the keyboard? Can anyone help post some code to handle this?
Thanks
Add a tap gesture in the parent view (of the UISearchbar)
[self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:searchBar action:#selector(resignFirstResponder)]];
I accomplished this by using a UITapGestureRecognizer:
UIGestureRecognizer* cancelGesture;
- (void) backgroundTouched:(id)sender {
[self.view endEditing:YES];
}
#pragma mark - UISearchBarDelegate
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
cancelGesture = [UITapGestureRecognizer new];
[cancelGesture addTarget:self action:#selector(backgroundTouched:)];
[self.view addGestureRecognizer:cancelGesture];
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
if (cancelGesture) {
[self.view removeGestureRecognizer:cancelGesture];
[cancelGesture release];
cancelGesture = nil;
}
}
The code is a bare, but you can see the intent. When the SearchBar starts editing, you attach a tap gesture recognizer to the view controller's view, and remove it when it stops editing.
There are a couple caveats that you can work around: doing this will make it so if you click anything besides the keyboard or the search bar's text field, the recognizer traps the click -- so if you use the clear, cancel, scope or results button they won't respond correctly.
In my particular scenario, I had a UITableView that was covering the exposed area of the view so I attached the gesture recognizer to it instead of the view controllers main view, isolating the area to which the gesture would respond.
An alternative idea I got from iphonedevbook, sample code project 04, was to use one big transparent button that lies behind all other controls which does nothing but resign all first responders if tapped. I.e. if the user taps anywhere where there isn't a more important control - which is the intuitive behavior - the search bar and keyboard disappear.
I ended up using a hybrid of Hauke's and Beau Scott's approach. There were two problems I ran into using their solutions:
1) If there's anything else on the screen, tapping it won't result in resignFirstResponder being called. For example, if the user taps a button rather than the space around the button, the button will eat the event. Beau Scott's solution addresses this issue, however.
2) Tapping the search bar itself will result in resignFirstResponder getting called. Clearly you don't want the keyboard to disappear when you tap UISearchBar. A small change described below addresses this.
I ended up setting up my view as follows. The parent view has two children - the UISearchBar and a subview which holds the rest of my UI elements. The subview takes up the entire screen below the UISearchBar. Then I used Beau Scott's exact code to add and remove the gesture recognizer, but instead of adding it to self.view I added it to the subview:
IBOutlet UIView *gestureRecognizer;
...
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
cancelGesture = [UITapGestureRecognizer new];
[cancelGesture addTarget:self action:#selector(backgroundTouch:)];
[gestureRecognizer addGestureRecognizer:cancelGesture];
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
if (cancelGesture) {
[gestureRecognizer removeGestureRecognizer:cancelGesture];
[cancelGesture release];
cancelGesture = nil;
}
}
First, you need a reference to the search bar. Let's assume that your controller object has an object reference UISearchBar *theSearchBar, and that you assign it when you create the UISearchBar object.
Next, you need to detect that the containing view has been touched. The view that is touched "knows", but you need get that information to the controller. Sadly, Apple didn't provide a simple way to do this, but it's not that hard either.
My solution is to replace the standard UIView that a UIViewController object normally creates with a UIControl, and then make the UIViewController respond to touch events.
MainController.m
- (void) loadView {
UIControl *control = [[UIControl alloc] initWithFrame: <desired frame>];
[control addTarget: self action: #selector(touchUpInside)
forControlEvents: UIControlEventTouchUpInside];
// or touch down events, or whatever you like
self.view = control;
[control release];
}
- (void) viewDidLoad {
[super viewDidLoad];
theSearchBar = [[UISearchBar alloc] initWithFrame: <desired frame>];
// insert code to finish customizing the search bar
[self.view addSubview: theSearchBar];
}
- (void) touchUpInside {
if [theSearchBar isFirstResponder] {
// grab any data you need from the search bar
[theSearchBar resignFirstResponder];
}
}
MainController.h
#interface MainController : UIViewController
{
UISearchBar *theSearchBar;
}
Clarification:
There is only a single object -- let's call the class MainController -- which is a subclass of UIViewController. All of the methods listed above are implemented in MainController. theSearchBar is declared as a UISearchBar* in the .h file.
Are you defining your view and controller using Interface Builder? If so, I suggest you learn how to NOT use it -- once you get into the kind of tricks we are discussing here, it becomes more of a hindrance than a help -- I don't use it at all, ever.
#Gia Dang's answer is the simplest, but I don't subclass the UIView, only the UIViewController, so my call is slightly different. Also, since I don't know the overhead for actually calling resignFirstResponder, I prefer to check first. It's more code, but since all of this is done on the main thread (which can slow down the UI), I'd rather check first.
#implementation MyController : UIViewController {
#private
UISearchController *_uiSearchController;
}
- (void)viewDidLoad {
// add tap on view to resign the responder if we're in the middle of typing in the search
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeKeyboardIfNeeded)];
[self.view addGestureRecognizer:tapGestureRecognizer];
}
- (void)closeKeyboardIfNeeded {
if (![_uiSearchController.searchBar isFirstResponder]) {
return;
}
[_uiSearchController.searchBar resignFirstResponder];
}
#end
As for the other answers, be careful about constantly recreating objects. There is always a performance hit, whether it's the creation itself or the garbage collection through ARC, and these things will slow down your main thread. Depending on what you're doing also on the main thread, it may have a significant performance impact.