Here's my latest problem with the iPhone SDK.
I've got a UISearchBar and its delegate all set up.
Also, when I load my view, I call
self.searchDisplayController.searchBar.showsScopeBar = YES;
That way, when my view is first presented, I see the scope bar, as expected.
But if touch inside the search bar and then outside it (or even if a perform a search and then cancel it), the scope bar gets hidden again.
So my question is: is it possible to have the scope bar always visible? Even after performing searches?
Thanks a lot.
The UISearchDisplayController is hiding the scope bar for you.
The way around this is to subclass UISearchBar and override the implementation of setShowsScopeBar:
#interface MySearchBar : UISearchBar {
}
#end
#implementation MySearchBar
- (void) setShowsScopeBar:(BOOL) show
{
[super setShowsScopeBar: YES]; // always show!
}
#end
Then, in Interface Builder, change the class of the Search Bar you have in your view (that is associated with the UISearchDisplayController) to the new class type -- MySearchBar in this example.
Related
There are many questions about overriding navigation controller's back button action, but everybody tells that there is no direct way to do this. I've implemented this by inheriting UINavigationController and overriding UINavigationBarDelegate method. But the problem is that interface of UINavigationController doesn't implement UINavigationBarDelegate protocol (but it implements this method, because this code works:), and I afraid that app will be rejected by Apple, considering that here is used undocumented API.
So, the question is: how do you think, is it private API usage or not? Here is the code:
#interface CustomNavigationController : UINavigationController <UINavigationBarDelegate>
#end
#implementation CustomNavigationController
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
BOOL shouldPop = [[self topViewController] enableBackButton]; // every view controller overrides this method to disable or enable pop of view controller
if (shouldPop) {
//here is the warning about "UINavigationController may not respond to selector"
return [super navigationBar:navigationBar shouldPopItem:item];
} else {
return NO;
}
}
#end
Apple's documentation on UINavigationController: This class is not intended for subclassing.
While this intention is not a ban and may not technically get you rejected (using doesn't appear to reference any private APIs) it should impact your design decision. You're likely to inherit unexpected behavior now or potentially in the future (if something changes to the internals of the superclass). Navigation controller's are handy things, but depending on what you need, you might just be better off rolling your own.
One alternative might be to employ a standard navigation controller, but set it's navigationBar property to hidden. You may then design and include your own navigation bar in a XIB with whatever buttons targeted at whatever actions you wish (e.g., a back button simply calling popViewController).
EDIT: Subclassing of UINavigationController is no longer discouraged.
This question has been asked a lot e.g. here but as far as I can see is yet to be answered in full.
I have a UITabBarController with a UINavigationController as the root vc for one of the tabs, which itself has a MKMapView as its root vc. The behaviour I want is for the map to partially curl upwards, while leaving the tab bar in place (similar to the Maps app).
So far all I have managed to get working is for the whole view to curl, which isn't as nice.
Solutions I have seen are to set the hidesBottomBarWhenPushed property to NO, which would make sense however this doesn't seem to work, (unless I am doing something wrong).
For clarity, my code is as follows:
MyVC *aView = [MyVC init];
aView.modalTransitionStyle = UIModalTransitionStylePartialCurl;
aView.hidesBottomBarWhenPushed = NO;
For the presenting part, I have tried the two alternatives below, neither of which seem to work:
[self presentModalViewController:updateStatus animated:YES];
[[self navigationController] presentModalViewController:updateStatus animated:YES];
Any help much appreciated.
I've scoured StackOverflow (and the Internet) for a solution to this problem. The question has been asked many times, but as you note, never sufficiently answered. Many solutions give an acceptable solution if it is unimportant whether, e.g., a lower toolbar curls up as well.
Others have provided a solution using UIView animations / CoreAnimation rather than UIModalTransitionStylePartialCurl as a modal transition style; this is at worst a solution not allowed in the App Store, and at best is not quite the same effect as one gets from UIModalTransitionStylePartialCurl (e.g. the shape of the curl is different).
None of these solutions have provided an answer that mimics Apple's solution in the Maps app (i.e., using UIModalTransitionStylePartialCurl but leaving an un-curled UIToolbar at the bottom of the screen).
I will continue in this tradition of incomplete answers, since you ask about a UITabBarController and my solution doesn't specifically address that case. It does, however, solve the problem I had, which was to get a half page curl with an un-curled toolbar at the bottom.
There must be a more elegant way to do this, but this is how I managed.
The rootViewController of my AppDelegate is a subclass of UIViewController, which I'll call TAContainerViewController. TAContainerViewController manages a) the actual contents of the screen (the "stuff to be curled"), TAContentViewController, and b) the contents "behind" the TAContentViewController (e.g. settings), which I'll call TAUnderCurlViewController.
My instance of TAContainerViewController had properties for a TAContentViewController and a TAUnderCurlViewController. The UIView that was my content was a subview of TAContentViewController's view property; likewise what the user sees under the curl is the view property of the TAUnderCurlViewController.
In the init method of TAContainerViewController I make sure to do the following:
_underCurlVC.modalTransitionStyle = UIModalTransitionStylePartialCurl;
And to curl the contents to reveal under the page, I set up an action that calls this code:
[self.contentVC presentModalViewController:self.underCurlVC animated:YES];`
where self is the TAContainerViewController, contentVC is an instance of TAContentViewController, and underCurlVC is an instance of TAUnderCurlViewController.
To dismiss the view, simply [self.contentVC dismissModalViewControllerAnimated:YES];.
Some strangeness seems to occur with the frame of contentVC when the modal view is dismissed, so I manually reset the frame when the modal view is dismissed.
I've posted a sample project with more details on Github. Hopefully someone can take this and turn it into a slightly more elegant solution, or expand it to work with a UINavigationController or UITabBarController. I think the trick is to pull the View Controllers out of the well-defined relationships in the Cocoa subclasses, so maybe subclassing those specialty View Controllers would do it.
Tim Arnold's response worked great for me, thanks!
One trap to watch out for: your modal page-curl transition will take over the whole screen if your content view controller is added as a child of the container view controller. You could just not add it as a child, but then none of the view lifecycle methods will get called on your content controller (e.g. viewDidLoad, viewWillAppear), which could be a problem.
Fortunately, there is a way around this. In your container controller:
Add your content controller as a child in viewDidLoad
Remove it as a child in viewDidAppear
Re-add it as a child in viewWillDisappear.
That way, your content controller gets its lifecycle methods called, while still being able to do a modal page-curl transition without taking up the whole screen.
Here is the entire code of a bare-bones solution:
#interface XXContainerController : UIViewController
#property (strong, nonatomic) UIViewController *contentController;
#property (nonatomic) BOOL curled;
#end
#implementation XXContainerController
#synthesize contentController = _contentController;
#synthesize curled = _curled;
- (void)viewDidLoad
{
[super viewDidLoad];
self.contentController = [self.storyboard
instantiateViewControllerWithIdentifier:#"SomeControllerInStoryboard"];
// Add content controller as child view controller.
// This way, it will receive all the view lifecycle events
[self addChildViewController:self.contentController];
self.contentController.view.frame = self.view.bounds;
[self.view addSubview:self.contentController.view];
[self.contentController didMoveToParentViewController:self];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Remove the content controller as child view controller.
// This way, the modal page curl transition will
// not take over the whole screen.
// NOTE: need to wait until content controller has appeared
// (which will happen later).
// Achieve this by running the code at the end of the animation loop
[UIView animateWithDuration:0 animations:nil completion:^(BOOL finished) {
[self.contentController removeFromParentViewController];
}];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Add the content controller as child view controller again
// so it receives the view lifecycle events
[self addChildViewController:self.contentController];
}
- (void)setCurled:(BOOL)curled
{
if (curled == _curled) return;
_curled = curled;
// Curl up the content view and show underneath controller's view
if (curled) {
// Note you can specify any modal transition in storyboard
// E.g. page curl, flip horizontal
[self.contentController
performSegueWithIdentifier:#"SomeModalSegueDefinedInStoryboard"
sender:self];
// Uncurl and show the content controller's view again
} else {
[self.contentController dismissModalViewControllerAnimated:YES];
// Have to do this, otherwise the content controller's view
// gets messed up for some reason
self.contentController.view.frame = self.view.bounds;
}
}
#end
This is for an addition to a legacy iPhone app with the architecture already defined (years ago, by somebody else.)
The main limitation is that the functionality of the main menu system is based on configuration files, so I can't call any specific initialisation code from the main menu.
This means that the view I am developing is stand-alone, and has to somehow manage its states with the information from the system.
Further, on each screen there is a "Settings" button, taking the user to a Settings pane, that is pushed on the navigation stack on top of "my" view. When the user closes the settings pane, my view reappears, as per normal navigation.
OK, so here is my problem:
When the user enters my view from the menu I want it to be reset so all input fields are empty.
If the user goes to the settings screen and returns to my screen, I want all previous input to be preserved, i.e., not reset to empty fields.
If the user then goes back to the main menu, and re-enters my screen, fields should be empty again.
Is there a robust, documented and preferably simple way to know if I should reset the fields in this scenario?
Can you check the navigation stack to see if the settings page is currently on the stack?
- (void)viewWillAppear:(BOOL)aAnimated
{
[super viewWillAppear:aAnimated];
NSArray* stack = [[self navigationController] viewControllers];
UIViewController* last = [stack lastObject];
}
Presumably viewDidDisappear is called when your view is hidden for some reason. You could presumably get the UIWindow and work up the view chain to find where your view is in the chain (if it is at all) and what is hiding it.
Non-trivial, though, and the sort of thing that will likely need to be "maze bright" vs robust.
(Though if a navigation controller is consistently used it becomes much simpler.)
In the view controller in question, how about you set up a delegate.
id delegate;
#property (nonatomic, assign) id delegate;
for the header, then synthesize in the implementation.
Whenever you push to this view controller, set self as the delegate from the pushing view. Then in this view controller, you can perform a check in the viewDidLoad or viewDidAppear: (or wherever you feel it would be necessary) with something like the following:
if ([self.delegate isKindOfClass:[SomeClass class]]) {
// now you can find which class sent to this view;
}
That should do the trick, so I hope it helps you out
EDIT: considering you are switching views without always using a nav controller, the above won't be valid all the time. In that case, you are probably better off using an internal property as well as an outlet to your settings pane. So in this view controller, you'll want something like this in the header:
BOOL shouldReset;
#property (readwrite) BOOL shouldReset;
In your viewDidLoad, you'll want to initialize this as shouldReset = YES. You should also put this in your viewDidDisappear: since it is your default behavior. When you present the settings pane, give the settings an outlet to the current view controller so you can, from within the settings (when you press the back button) set [self.otherViewController setShouldReset:NO]. Then in your viewDidAppear: for the original view controller in question, you can check to see if it should reset its fields or not
My problem is that I cannot access any of the controls in a view defined using interface builder. This is the .h code for the Navigation bar (as an example):
#import <UIKit/UIKit.h>
#interface myController : UIViewController {
IBOutlet UINavigationBar *tTitle;
}
#property (nonatomic,retain) UINavigationBar *tTitle;
#end
The implementation (.m) is:
#import "myController.h"
#implementation myController
#synthesize tTitle;
- (void)dealloc {
[tTitle release];
[super dealloc];
}
- (void)viewDidLoad {
tTitle.topItem.title=#"This is my title";
}
In viewDidLoad tTitle (and my other outlets) are always 0x0. I have omitted the two text fields and the button for brevity.
This exact code works in another view in the app without issue. In IB I right click on the file owner icon and it shows my outlets correctly (and the single button action). Yet at run time - nada. I click the button and no response. The title is still the default title. I cannot set the text fields text property because the fields are all 0x0.
The view is linked to the view controller. As near as I can tell everything is identical between the two views that are doing the same thing. Obviously something is awry, but I can't figure it out. Any help would be appreciated.
Okay. I did the ROI and figured it would be easier to delete and recreate the view (one control at a time) in an attempt to see where it was going awry. The answer is the title bar title set worked from the get-go. I have no idea why the other class didn't work. But discretion is the better part of valor in this situation. The new class is working fine. Thanks for everyone's input.
I have a simple UITableViewController in a UINavigationController that displays a list of strings from an array with the default Edit/Done button on the right-hand side of the navigation bar.
When pressing the Edit button, the UITableView animates correctly and shows the red minus icons to delete. Pressing the delete button removes the row from the table view and the array (implemented in the tableView:commitEditingStyle:forRowAtIndexPath: method of the UITableViewController).
I would now like to allow the user to add a row to the view (and add the string to the underlying array), but I'm not sure how to go about doing so. The commitEditingStyle method has else if (editingStyle == UITableViewCellEditingStyleInsert), but I don't know how I can get the user to input the string.
I've read the Table View Programming Guide (more specifically the example of adding a table-view row), but this seems to require a whole new UIViewController subclass just to get a string from the user.
Is there no easier way?
Creating another view controller is probably going to be the easiest way in the long run. You can present it modally by calling
SomeViewController* theViewController = [[SomeViewController alloc] init];
[self presentModalViewController: theViewController animated: YES];
[theViewController release];
When the theViewController is ready to go away it can call
[[self parentViewController] dismissModalViewControllerAnimated: YES];
OR
you can setup a protocol for your new view controller so it can notify your original view controller of completion and send a value back, if you wanted an NSString back you might use
#protocol MyViewControllerDelegate
- (void)myViewControllerDelegate: (MyViewController*)myViewController didFinishWithValue: (NSString*)theString;
#end
MyViewController would then have a delegate property
#interface MyViewController
{
id<MyViewControllerDelegate> delegate;
}
#property(nonatomic,assign) id<MyViewControllerDelegate> delegate;
#end
If you use the protocol method your original view controller will adopt that protocol and will dismiss the modal view itself when it receives this message.
I hope that helps out, it may seem a little complicated at first, but it makes gathering data very easy.
You could use a UIAlertView or similar class yourself. Just pop up the modal view to request the string, establish the right callbacks, then pop it in your dataSource.
You can also insert a cell with a UITextView and a "Tap to Edit" placeholder, then on the textView Callbacks, remove the textView and display the string. Further editing would need to drill down or do something else