Which event get called when we hit UISearchBar - iphone

In my application, I need to do some activity i.e pushing otherview controller,when I click a UISearchbar which is added on view.
what is best approach to achive this.
As one of thing is when we click UISearchbar "searchBarTextDidBeginEditing" get fired,but with my scenario when I push view controller in "searchBarTextDidBeginEditing" and come back searchBarTextDidBeginEditing get called again, so seems it is not ideal place to push view controller.
This is maincontroller
// Search bar
iSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, 40)];
iSearchBar.delegate = self;
iSearchBar.showsCancelButton = NO;
iSearchBar.autocorrectionType = UITextAutocorrectionTypeNo;
iSearchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self addSubview:iSearchBar];
when I click UISearchBar then it calls
- (void)searchBarTextDidBeginEditing:(UISearchBar*)searchBar
{
[self ShowMySearch];
}
In ShowMySearch , I am pushing some other controller lets say searchcontroller and when pop this searchcontroller and come back to maincontroller
"searchBarTextDidBeginEditing" get call again and searchcontroller is pushed again and causing issue. this behavior is seen only on 3.1.1
Thanks,
Sagar

I think calling [self ShowMySearch] in "searchBarTextDidBeginEditing" is a bit too late.
I suppose that "searchBarTextDidBeginEditing" is called on response to the search bar becoming first responder. Since it is the first responder when the search controller is pushed, it probably become first responder again when your search controller is poped out...thus calling "searchBarTextDidBeginEditing" once again.
To achieve this, I'd use :
(BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
This method is called after the search bar is tapped but before it becomes the first responder. And if you return NO, it will never become the first responder :
- (BOOL)searchBarShouldBeginEditing:(UISearchBar*)searchBar {
[self ShowMySearch];
return NO;
}
Let me know if this works !

For Swift 5.
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
handleShowSearchVC()
return false
}
#objc func handleShowSearchVC() {
let modalUserSearchController = UserSearchController(collectionViewLayout: UICollectionViewFlowLayout())
modalUserSearchController.modalPresentationStyle = .overCurrentContext
//Mini app panel.
//vc.view.frame = CGRectMake(0, vc.view.frame.size.height - 120, vc.view.frame.size.width, 120)
//Present #1
// present(modalUserSearchController, animated: true, completion: nil)
//Presentation #2
navigationController?.pushViewController(modalUserSearchController, animated: true)
}

Related

How to identify that an UIViewController is presented

I have created an UIViewController sub class which can either be pushed in a navigation stack of a UINavigationController or presented(modally) from any UIViewController. I need to identify whether my view controller is presented, if it is presented I need to add a toolbar with a close button at the top of the view controller. (else if it is pushed in navigation stack then the default close button will get added, by using that the user can go back.)
In all the available versions say 4.3, 5.0, till 6.0, from inside an UIViewController sub class, Can I assume that the view controller is presented(modally) if the following condition is satisfied.
if(self.parentViewController == nil || self.navigationController == nil)
With iOS 5, UIViewController gained a readonly property named presentingViewController, that replaces the older semantics of parentViewController (which now describes containment). This property can be used when a view controller needs to get at the view controller that’s presenting it — note: this will often be something else than what you’d expect, if you’re new to the API!
In addition, the isBeingPresented property had been introduced to pretty much solve the class of situations you’re currently in. Check for this property in your view controller’s viewWillAppear:.
Update
I overread that you seem to target iOS 4.3 as well:
In that case, you need to guard the call to isBeingPresented with an if ([self respondsToSelector:…]) you can then in the else block check for whether the parentViewController is not nil.
Another approach to backwards compatibility might be to override +resolveInstanceMethod: to add an implementation for -isBeingPresented at runtime. This will leave your calling sites clean, and you’d get rid of runtime-magic as soon as you let go of ancient iOS support ;-)
Note, though, that there are edge cases to this, and you initial approach as well, when running on iOS <5:
The view controller can be presented contained in any other view controller—including navigation controllers. When that last case happens, you’re out of luck: parentViewController will be nil, while navigationController will not. You can try to add gobs of unwieldy code to mitigate this limitation in older iOSes…or you could just let it go.
I use the this code to check whether the UIViewController is presented.
if (uiviewcontroller.presentingViewController != nil) {
// do something
}
I had a similar case, however the view controller that I presented is wrapped in it's own navigation controller. So in that view controller when I need to determine whether or not to add the close button vs a back button, I just check the navigation controllers stack size. If the screen is presented, the stack size should be one (needs close button)... and if it is pushed using an existing navigation controller, then stack size will be larger than one (needs back button).
BOOL presented = [[self.navigationController viewControllers] count] == 1;
To handle this kind of behavior, I usually set/reset a BOOL toggling it in viewWillAppear/viewWillDisappear methods.
By the way, your test condition seems incorrect. I think you should use
if(self.parentViewController != nil || self.navigationController != nil)
Why can't you just always add the toolbar to your view controller? Is there any case the view is loaded but never presented?
In Swift on iOS 9 (or later):
if viewController.viewIfLoaded?.window != nil {
// viewController is visible
}
#saikamesh.
As you use UINavigationController to navigate your viewControllers, I think you can use topViewController (Doc here) and visibleViewController (Doc again) to reach your intention.
You mention that :
when it is pushed in navigation stack then the default close button
will get added, by using that the user can go back
If the instance of the the specific UIViewController is important, I think it better to create a shared singleton instance and provide a global presented flag:
id specificVC = [SpecificViewController sharedInstance];
if (specificVC.isPushed) {
[self.navController popToViewController:specificVC animated:YES];
}
and to check if it is presented:
if ([self.navController.visibleViewController isKindOfClass:[SpecificViewController class]]) {
// Hide or add close button
self.isPresented = YES;
}
Or, you can read the much accepted answer.
:) Hope helps.
Please check this way:
for (UIViewController*vc in [self.navigationController viewControllers]) {
if ([vc isKindOfClass: [OffersViewController class]]){ //this line also checks OffersViewController is presented or not
if(vc.isViewLoaded){
NSLog(#"Yes");
}
}
}
You could do it like this, it's fast and safe
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
// Find the top controller on the view hierarchy
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
// If the top controller it is not already presented
if (![topController isKindOfClass:[YourViewController class]]) {
// Present it
[topController presentViewController:yourViewController animated:YES completion:nil];
}
else {
// do some stuff here
}
You can at any point check whether you have a modal view controller presented or not by using the modalViewController property from your navigation controller.
Ex:
UIViewController *presentedController = self.navigationController.modalViewController;
if (presentedController) {
// At this point, you have a view controller presented from your navigation controller
if ([presentedController isKindOfClass:[controllerYouWantToCheck class]]) {
// add your toolbar/buttons/etc here
}
}
One elegant answer that I haven't seen here:
// Edit: Added 2 other modal cases
extension UIViewController {
var isModal: Bool {
return self.presentingViewController?.presentedViewController == self
|| (navigationController != nil && navigationController?.presentingViewController?.presentedViewController == navigationController)
|| tabBarController?.presentingViewController is UITabBarController
}
}
credit: based on this gist
As Martin Reed said, this is the best way
BOOL presented = [[self.navigationController viewControllers] count] == 1;
if (presented) {
[self dismissViewControllerAnimated:YES completion:^{
// do whatever you need here
}];
}
else {
[self.navigationController popViewControllerAnimated:YES];
}
If it was me I'd have a custom init method and use that when creating the vc.
vc = [[[MyUIViewControllerSubClass alloc] init] initWithToolbarAndCloseButton:YES];
Small modification in #AmitaiB answer to create a function,
func isModallyPresented(tmpVC:UIViewController) -> Bool {
return tmpVC.presentingViewController?.presentedViewController == tmpVC
|| (tmpVC.navigationController != nil && tmpVC.navigationController?.presentingViewController?.presentedViewController == tmpVC.navigationController)
|| tmpVC.tabBarController?.presentingViewController is UITabBarController
}
Just check by calling:
if(isModallyPresented(tmpVC:myTopVC)){
//return true if viewcontroller is presented
}

How to hide inputAccessoryView without dismissing keyboard

I am using a toolbar in the inputAccessoryView property of a textView. When the keyboard shows, it displays the toolbar as expected. When the device is rotated I want to remove the toolbar. I tried:
myTextView.inputAccessoryView.hidden = !layoutIsPortrait;
This does hide the toolbar, but leaves the outline of the taller keyboard behind. The keyboard is apparently still sized to fit the toolbar. It looks bad and interferes with touch events of underlying responders.
myTextView.inputAccessoryView = nil;
Works only if I resignFirstResponder, then becomeFirstResponder again. This is not acceptable. I lose the cursor placement and content of the textView, keyboard flashes out and back.
[myTextView.inputAccessoryView removefromSuperview];
Does nothing at all.
I saved a reference to the toolbar in a iVar and addressed that instead,
[myIvarReference removeFromSuperview];
That works, but again the taller outline of the keyboard is faintly visible. This time it does not interfere with touches of other views. So now this is a working solution but visually unacceptable.
What else can I try to show and hide the inputAccessoryView at will?
Screenshot- the faint line above the keyboard is remnant of the removed toolbar
myTextView.inputAccessoryView = nil;
[myTextView reloadInputViews];
This removes the toolbar from the view and reloads the view. This way you don't need to call resignFirstResponder and becomeFirstResponder. Additionally, this will still keep your cursor placement and content.
None of the answers above were working for me and reloadInputViews was causing weird issues. Eventually I got it to show and hide and have touches passthrough by doing:
Hide it:
[textview.inputAccessoryView setHidden:YES];
[textview.inputAccessoryView setUserInteractionEnabled:NO];
Show it:
[textview.inputAccessoryView setHidden:NO];
[textview.inputAccessoryView setUserInteractionEnabled:YES];
For me Eric's solution never actually reset the frame or the touch areas. Presumably it's a bug with how Apple handles things. However, I found a workaround that solved the problem for me. When I set a new inputAccessoryView without a frame, reloadInputViews worked fine:
myTextView.inputAccessoryView = [[UIView alloc] initWithFrame: CGRectZero];
[myTextView reloadInputViews];
Never found a way to alter the frame of the keyboard. Ultimately decided to forego the inputAccessoryView, add my toolbar directly to the view as a subview and animate it myself along with the keyboard directly. This keeps the two independent and so, no more line.
Xamarin code is
Control.InputAccessoryView = new UIView(CGRect.Empty);
Control.ReloadInputViews();
Oddly enough, none of these approaches worked in my case.
I have a searchcontroller which pops up a standard Apple iOS keyboard if a particular search scope is selected, and a custom keyboard view with a collection view as the input field in cases of other scopes being selected.
In both cases, an undesired accessory view was drawn on the screen when the input view was displayed.
So,
self.mySearch.searchbar.inputAccessoryView = nil // did not work
[self.mySearch.searhbar.inputAccessoryView setHidden:YES] // did not work
self.mySearch.inputAccessoryView = nil // did not work
self.mySearch.searchbar.inputAccessoryView.frame = CGRectZero //did not work
[self.mySearch reloadInputViews]
and various combinations thereof etc, etc.
What did work was to delete to individual accessory items from the accessory view:
// insert after assignments of mySearch delegates
UITextInputAssistantItem *junk = [self.mySearch inputAssistantItem];
junk.leadingBarButtonGroups = #[];
junk.trailingBarButtonGroups = #[];
mTextView.inputAccessoryView = [[UIView alloc] initWithFrame:CGRectZero];
[mTextView reloadInputViews];
works for me, setting inputAccessoryView to nil will not work, I just don't know why.
Based on Eric Appel's answer:
myTextView.inputAccessoryView = nil;
[myTextView reloadInputViews];
hideInputAccessoryView = YES;
Further modify:
- (BOOL)canBecomeFirstResponder
{
BOOL showInputAccessoryView = YES;
if (hideInputAccessoryView)
showInputAccessoryView = NO;
return showInputAccessoryView;
}
This should hide InputAccessoryView even when the keyboard is resigned.
Xcode 11.6 and iOS 13.6
I was trying to add two toolbars when the keyboard appears.
First, I added on viewDidLoad
self.textView.inputAccessoryView = self.addDefaultToolbar()
When the keyboard appeared, then I selected a text in UItextView and trying to add the second toolbar option
Here's code which is NOT showing a Toolbar on a selection of text.
func textViewDidChangeSelection(_ textView: UITextView) {
if let selectedRange = textView.selectedTextRange{
let selectedText = textView.text(in: selectedRange)
if selectedText != nil && selectedText!.count > 0{
print("selectedText - \(selectedText!)")
if self.defaultToolBar != nil{
self.defaultToolBar?.removeFromSuperview()
self.defaultToolBar = nil
}
self.textView.inputAccessoryView = self.addSelectionToolbar()
}
else{
print("not selectedText - \(selectedText!)")
if self.selectionToolBar != nil{
self.selectionToolBar?.removeFromSuperview()
self.selectionToolBar = nil
}
self.textView.inputAccessoryView = self.addDefaultToolbar()
}
}
}
After adding a self.textView.reloadInputViews(), I was able to see change second toolbar and vice versa.
Working code.
func textViewDidChangeSelection(_ textView: UITextView) {
if let selectedRange = textView.selectedTextRange{
let selectedText = textView.text(in: selectedRange)
if selectedText != nil && selectedText!.count > 0{
print("selectedText - \(selectedText!)")
if self.defaultToolBar != nil{
self.defaultToolBar?.removeFromSuperview()
self.defaultToolBar = nil
}
self.textView.inputAccessoryView = self.addSelectionToolbar()
self.textView.reloadInputViews()
}
else{
print("not selectedText - \(selectedText!)")
if self.selectionToolBar != nil{
self.selectionToolBar?.removeFromSuperview()
self.selectionToolBar = nil
}
self.textView.inputAccessoryView = self.addDefaultToolbar()
self.textView.reloadInputViews()
}
}
}
Swift 5
I use this:
view.autocorrectionType = .no

Removing cancel button in UIImagePickerController?

I am developing an app for ios 5.
I need to remove the cancel button on UIImagePickerController i searched for this problem on the forum but didnt get exact answer can someone please help me with this?
That is because it is not possible to remove that cancel button. That is an inbuilt function and you can not make changes in the same.
Swift Version, compatible 4+
To remove the navigation bar :
imagePicker.view.subviews
.filter { $0.isKind(of: UINavigationBar) }
.forEach { $0.isHidden = true }
for removing the buttons only :
imagePicker.view.subviews
.filter { $0.isKind(of: UIButton) }
.forEach { $0.isHidden = true }
Voilà
I will give the best method to achieve this:
First create a subclass of the UiImagePickerController
in the subclass respond to the UINavigationBarDelegate method
- (BOOL)navigationBar:(UINavigationBar *)navigationBar
shouldPushItem:(UINavigationItem *)item
you dont have to do anything like setting the delegate or adding a protocol, just override the method inside your custom UIImagePcikerController.
in this method return NO for the cancel item (which is the first one)
I did this with ELCImagePickerController
There isn't a way to remove only the cancel button. UIImagePickerController exposes a property called showCameraControls, which will hide the bottom bar with the cancel button and the camera button, as well as the controls for flash, HDR, and flip camera, giving you just the camera preview.
If you want to provide an experience without a cancel button, you'll have to create a camera overlay view of what you want.
Assuming you have code invoking UIImagePickerController, you can turn off camera controls like this:
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
[imagePicker setShowsCameraControls:NO];
Assuming you'll overlay it with your own view without the cancel button, you'll add this (assuming you have a UIView called cameraOverlay:
[imagePicker setCameraOverlayView:cameraOverlay];
This will hide Navigationbar itself along with the Cancel and Title
let videoPicker = UIImagePickerController()
for view in videoPicker.view.subviews {
if let navBar = view as? UINavigationBar {
navBar.isHidden = true
}
}
If you want to remove the cancel button alone, dig deep into navBar
When you present the UIImagePickerView try puting the below code
for (UIView *subview in view.subviews) {
NSLog(#"subviews=%#",subview);
NSString *className = [NSString stringWithFormat:#"%#", [subview class]];
}
By the above code you can get the navigation controller used for displaying the cancel button.. Once you get the navigationController Set its leftside button to nil..
I have not used it but hope it can be of help to you

Pre-render UITabBarController views?

I have an app with a UITabBarController that manages some UINavigationControllers, which in turn manage various UIViewControllers.
On 3G phones, the first time I view any particular view via a TabBar button, it's laggy, but thereafter, it's snappy. This isn't noticeable with 3GS phones. My question is how can I force these views to pre-render? I have tried triggering the loadView functions by calling them in a different thread on start-up, but this doesn't do anything I don't think.
For clarity, here is an abbreviated snippet from my code to show what I'm doing. I have five view controllers, but I'm showing just the code for two. The poiVC is just a standard UITableViewController subclass - I don't even have a custom init or loadView function for it.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
self.mapVC = [[MapViewController alloc] init];
NavControlBar * mapNavBar = [[[NavControlBar alloc] initWithViewController:mapVC
withControlBar:[mapVC initBar]]autorelease];
self.poiVC = [[POIViewController alloc] init];
NavControlBar * poiNavBar = [[[NavControlBar alloc] initWithViewController:poiVC
withControlBar:[poiVC initBar]]autorelease];
NSArray *tabViewControllerArray = [NSArray arrayWithObjects:mapNavBar, poiNavBar, nil];
self.tbc.viewControllers = tabViewControllerArray;
self.tbc.delegate = self;
[mapVC release];
[poiVC release];
[window addSubview:tbc.view];
}
Can I get the poiVC to render while the user is looking at the first screen, so the transition will be fast?
[self.mapVC view];
[self.poiVC view];
You can simply ask for the view of the view controller and not do anything with it. This will return the view, and hence load it for you if needed. The disadvantage is of course that you'll increase your startup time. Also note that your view may be unloaded when memory runs low, which causes lagginess again when switching to those tabs, but (at least tries to) keep your app running (generally considered a good thing).
I've been struggling with the same issue. All of the answers I've seen talk about trying to preload the view by making a call viewController.view, but that's not the main bottleneck, and it only saves a few hundred milliseconds at best. The majority of the actual rendering work happens between viewWillAppear and viewDidAppear, so you need to trigger a render if you want to avoid the lag during your initial transition.
To do this, you need to add your view controllers as child view controllers inside your main view. I found that the best place to do this was in the viewDidAppear callback on your main view controller, because this will not slow down the loading of your main view. And even though you're loading and adding the child views on the main thread, it doesn't seem to block the UI. (I've been testing on a view that has a background video playing.)
Here's the Swift code I've written to pre-render some view controllers:
enum ViewPreloadingState {
case Pending, Preloading, Loaded
}
var viewPreloadingState = ViewPreloadingState.Pending
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if viewPreloadingState == .Pending {
viewPreloadingState = .Preloading
addChildViewController(loginViewController)
view.addSubview(loginViewController.view)
loginViewController.didMoveToParentViewController(self)
addChildViewController(signupViewController)
view.addSubview(signupViewController.view)
signupViewController.didMoveToParentViewController(self)
// Place view controllers off screen
let f = view.frame,
offScreenFrame = CGRect(x: f.width, y: 0, width: f.width, height: f.height)
loginViewController.view.frame = offScreenFrame
signupViewController.view.frame = offScreenFrame
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if viewPreloadingState == .Preloading {
viewPreloadingState = .Loaded
loginViewController.willMoveToParentViewController(nil)
loginViewController.view.removeFromSuperview()
loginViewController.removeFromParentViewController()
signupViewController.willMoveToParentViewController(nil)
signupViewController.view.removeFromSuperview()
signupViewController.removeFromParentViewController()
}
}

Hiding UINavigationItem's bar button

I have added a BarButton item to the left of the nav.bar through Interface Builder and in the code I want this only to show in my table view's edit mode. But I didn't find any hidden property to set the leftBarButtonItem (like: self.navigationItem.leftBarButtonItem.hidden = YES).
I can only set enabled property. Anybody know how to control the hide and show property of the leftBarButtonItem, please help.
This works I tried it myself
self.navigationItem.leftBarButtonItem = nil;
self.navigationItem.hidesBackButton = YES;
I'm pretty sure the only way to "hide" it is to nil it out.
self.navigationItem.leftBarButtonItem = nil;
Though it's not a perfect answer to your question, since that basically gets rid of your button instead of hiding it. You'll either have to recreate it or keep your original button around and simply set the leftBarButtonItem back to your UIBarButtonItem.
I have a easy function to make this.
I have a navigation like this.
It comes form Interface Builder, it has a background image.
#IBOutlet weak var memberBtn: UIBarButtonItem!
you can hide/show it by:
func hideMemberBtn() {
memberBtn.isEnabled = false
memberBtn.tintColor = UIColor.clear
}
func showMemberBtn() {
memberBtn.isEnabled = true
memberBtn.tintColor = UIColor.white
}
It's easy but it work for me. You can change tintColor as you needed.
Hope for help :]
You can use
// Hide
self.navigationItem.leftBarButtonItem = nil;
// Show
self.navigationItem.leftBarButtonItem = self.myBarButtonItem
The key is making sure that you have a strong reference to the button item before nilling leftBarButtonItem.
#property (strong, nonatomic) IBOutlet UIBarButtonItem *myBarButtonItem;
I just created my own "hide" function show below:
- (void)hideClearButton:(BOOL)hide {
if (hide) {
self.navigationItem.leftBarButtonItem = nil;
}
else {
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]
initWithTitle:NSLocalizedString(#"Clear", #"Recents")
style:UIBarButtonItemStylePlain
target:self action:#selector(clearAll:)];
}
}
You can just call it like:
[self hideClearButton:YES]; //hide it
or
[self hideClearButton:NO]; //show it
There's nothing in the documentation to suggest bar items have a hidden property.
Why not set
self.navigationItem.leftBarButtonItem = nil;
when the user isn't editing, then set
self.navigationItem.leftBarButtonItem = whateverBarButtonItem;
when the user is editing? This requires either re-creating the button each time or storing it for the duration of the view's lifecycle. Neither is terribly painful, but no, not nearly as easy as a .hidden property.
You can use
[self.navigationItem.leftBarButtonItem setEnabled:TRUE];
as there is no other way to hide it. so just disable it.
To hide/disable
[self.navigationItem.leftBarButtonItem setEnabled:FALSE];
To show/enable
[self.navigationItem.leftBarButtonItem setEnabled:TRUE];
Well making it nil was not a option because i wanted to show it again and didnt want to create a button again.
so what i did was
UIBarButtonItem *barButton = (UIBarButtonItem *)self.navBar.topItem.leftBarButtonItem;
barButton.customView.hidden = true;//Hide
barButton.customView.hidden = false;//Show
Works for me. (my leftBarButtonItem was created using customView)
Hope it helps.
This solution work for me
UIView *myView = [[UIView alloc] initWithFrame: CGRectMake(0, 0, 300, 30)];
UIBarButtonItem *btnL = [[UIBarButtonItem alloc]initWithCustomView:myView];
self.navigationItem.leftBarButtonItem = btnL;
func showOrHideButton() {
isEnabled ? showButton() : hideButton()
}
func showButton() {
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(action))
}
func hideButton() {
navigationItem.leftBarButtonItem = nil
}