Change interactivePopGestureRecognizer direction - ios

My app supports both English and Arabic. interactivePopGestureRecognizer works properly when using English, ie on swiping from left to right, it pops viewController. But when i am using arabic, I have changed the semanticContentAttribute from right to left.
if([[[NSUserDefaults standardUserDefaults] objectForKey:#"LanguageCode"] isEqualToString:#"en"])
{
[[UIView appearance] setSemanticContentAttribute:UISemanticContentAttributeForceLeftToRight]; //View for English language
}
else
{
[[UIView appearance] setSemanticContentAttribute:UISemanticContentAttributeForceRightToLeft]; //mirror view for Arabic language
}
But the interactivePopGestureRecogniser is still from left to right. How can I change the direction of interactivePopGestureRecogniser such that it supports Arabic? I want to swipe from right to left to pop view controller on using Arabic language.

I found the solution for it after searching long time, if someone seeks.
The previous answer may cause the UI to hang / freeze.
The reason that the UI freezes / hangs is because the UINavigationController is missing a check for the root view controller when the gesture is executed on the root view. There a few ways to fix that, the following is what I did.
You should subclass UINavigationController, this is the right way to go and add implement as followed:
class RTLNavController: UINavigationController, UINavigationControllerDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Adding swipe to pop viewController
self.interactivePopGestureRecognizer?.isEnabled = true
self.interactivePopGestureRecognizer?.delegate = self
// UINavigationControllerDelegate
self.delegate = self
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
navigationController.view.semanticContentAttribute = UIView.isRightToLeft() ? .forceRightToLeft : .forceLeftToRight
navigationController.navigationBar.semanticContentAttribute = UIView.isRightToLeft() ? .forceRightToLeft : .forceLeftToRight
}
// Checking if the viewController is last, if not disable the gesture
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if self.viewControllers.count > 1 {
return true
}
return false
}
}
extension UIView {
static func isRightToLeft() -> Bool {
return UIView.appearance().semanticContentAttribute == .forceRightToLeft
}
}
Resources:
Original question:
Slide gesture with custom back button freezes the root view controller
The answer is used for the solution:
https://stackoverflow.com/a/27683249/1138498
Other solution which may work better (But it's in Objective-C):
https://stackoverflow.com/a/28364126/1138498

After a lot of trials, the only solution worked for me was this:
Swift 3:
extension UIViewController {
open override func awakeFromNib() {
super.awakeFromNib()
navigationController?.view.semanticContentAttribute = .forceRightToLeft
navigationController?.navigationBar.semanticContentAttribute = .forceRightToLeft
}
}
You can exclude the semantic attribute for certain types like:
UIView.appearance(whenContainedInInstancesOf: [UITableViewCell.self]).semanticContentAttribute = .forceLeftToRight

Related

Swift iOS -SplitViewController Won't Let Me Hide StatusBar?

I followed this tutorial to smoothly hide the statusBar smoothly hide statusBar and everything works fine when I use it on practice projects. I use the code in other project's that do not have SplitVC but have a tabBar and uses a navVC & tableView and everything works fine. In those I can successfully make it appear/disappear.
In my actual project I'm using a SplitViewController for iPad. I noticed when I implemented the directions from the link to my SplitViewController the statusBar wouldn't hide. I then made a new project using Apple's default MasterDetailApp to make sure I wasn't doing anything wrong but it doesn't work there either. I kept all of Apple's original code and only added in the necessary methods to make the statusBar appear/disappear
in info.plist I added the View controller-based status bar appearance and set it to YES
in storyboard I added a purple button to the DetailVC to trigger the statusBar disappearance. I also added in the method to make the backBar button disappear/reappear
I added all the methods to make the statusBar disappear/disappear to the DetailVC scene.
I added a tapGesture to the scene to make the statusBar and backButton reappear
I clicked the Plus button on the Master scene, a date appeared, clicked it to get to the DetailVC, pressed the purple buttonPressed to hide the statusBar and backButton but only the backButton gets hidden. I touch the background and the backButton reappears. The statusBar doesn't move.
I kept all the original code from Apple's project's and added mines below it:
class DetailViewController: UIViewController {
//MARK:- Apple's code
#IBOutlet weak var detailDescriptionLabel: UILabel!
func configureView() {
if let detail = detailItem {
if let label = detailDescriptionLabel {
label.text = detail.description
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
configureView()
// make backButton and statusBar reappear when scene is tapped
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(showBackButtonAndStatusBar))
view.addGestureRecognizer(tapGesture)
}
var detailItem: NSDate? {
didSet {
configureView()
}
}
//MARK:- Outside of the tapGesture in viewDidLoad everything below here is what I added
// bool to determine wether to hide the statusBar or not
var statusBarShouldBeHidden = false
// api method to allow the staus bar to be hidden
override var prefersStatusBarHidden: Bool{
return statusBarShouldBeHidden
}
// api method to animate status bar appearance/disappearance
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{
return .slide
}
#IBAction func buttonTapped(_ sender: UIButton) {
// 1. hide backBar button
navigationItem.setHidesBackButton(true, animated: false)
// 2. set bool to true
statusBarShouldBeHidden = true
UIView.animate(withDuration: 0.25){
// 3. api method to allow the statusBar to disappear
self.setNeedsStatusBarAppearanceUpdate()
}
}
//called when background is touched and added to tapGesture in viewDidLoad
#objc func showBackButtonAndStatusBar(){
// 1. set bool to false
statusBarShouldBeHidden = false
UIView.animate(withDuration: 0.25){
// 2. bring statusBar back
self.setNeedsStatusBarAppearanceUpdate()
}
// 3. bring backButton back
navigationItem.setHidesBackButton(false, animated: true)
}
}
How can I get the SplitViewVC to let me hide the statusBar?
It appears that you are trying to hide the status bar through the detail view controller. The status bar in the user interface is controlled only by the split view controller because it is on top of the view controller hierarchy. Therefore, the easiest way to control the behavior of the status bar is to subclass UISplitViewController and then override the prefersStatusBarHidden computed property in the subclass. Also, make sure you go to your storyboard and change the split view controller's custom class field in the Identity inspector to your subclass.
---Updated Answer---
#LanceSamaria Okay, I took your code above and tweaked some things. First of all, I only added the button action and not the tap gesture. Also, I commented out the hiding the back button, because this is important in the UI in order to be able to go back to the master view. Anyway, now when you click the button, the SplitViewController will hide the status bar. If you click the button again, then the status bar will reappear.
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var detailDescriptionLabel: UILabel!
var statusBarShouldBeHidden = false
func configureView() {
// Update the user interface for the detail item.
if let detail = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.description
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.configureView()
}
/* override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{
return .slide
} */
var detailItem: NSDate? {
didSet {
// Update the view.
self.configureView()
}
}
#IBAction func buttonTapped(_ sender: UIButton) {
// 1. hide backBar button
//navigationItem.setHidesBackButton(true, animated: false)
// 2. set bool to true
statusBarShouldBeHidden = !statusBarShouldBeHidden
UIView.animate(withDuration: 0.25){
// 3. api method to allow the statusBar to disappear
guard let svc = self.splitViewController as? SplitViewController else { return }
svc.statusBarShouldBeHidden = self.statusBarShouldBeHidden
svc.setNeedsStatusBarAppearanceUpdate()
}
}
}
Also, one more really important thing. Below is my code for the split view controller subclass. Note that I use the same variable name "statusBarShouldBeHidden" in both the split view controller and the detail controller.
import UIKit
class SplitViewController: UISplitViewController {
var statusBarShouldBeHidden = false
override func viewDidLoad() {
super.viewDidLoad()
}
override var prefersStatusBarHidden: Bool {
return statusBarShouldBeHidden
}
}
Thank you for posting this question. This has helped my learn a lot trying to solve this problem. Please let me know if you still have a question about what this.

Hanging (Freezing) issue UISemanticContentAttribute = .ForceRightToLeft

I am internally switching the App Language (LTR-RTL) and then re-initializing the storyboard.
here is the piece of code:
let semanticContentAttribute: UISemanticContentAttribute = language == .Arabic ? .ForceRightToLeft : .ForceLeftToRight
UIView.appearance().semanticContentAttribute = semanticContentAttribute
UINavigationBar.appearance().semanticContentAttribute = semanticContentAttribute
The issue is, all the presented view controllers freezes for 3-6 seconds while dismissing it.
What is causing this?
Setting semanticContentAttribute on the appearance() proxy is not supported. You're going to run into many other issues and bugs since the app still believes it's running in a language that isn't the one you're overriding.
Adding a language switcher to your app is only going to make it more confusing; users expect their apps to follow the language that their device is set to.
I found the solution for it after searching long time, if someone seeks.
The reason that the UI freezes / hangs is because the UINavigationController is missing a check for the root view controller when the gesture is executed on the root view. There a few ways to fix that, the following is what I did.
You should subclass UINavigationController, this is the right way to go and add implement as followed:
class RTLNavController: UINavigationController, UINavigationControllerDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Adding swipe to pop viewController
self.interactivePopGestureRecognizer?.isEnabled = true
self.interactivePopGestureRecognizer?.delegate = self
// UINavigationControllerDelegate
self.delegate = self
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
navigationController.view.semanticContentAttribute = UIView.isRightToLeft() ? .forceRightToLeft : .forceLeftToRight
navigationController.navigationBar.semanticContentAttribute = UIView.isRightToLeft() ? .forceRightToLeft : .forceLeftToRight
}
// Checking if the viewController is last, if not disable the gesture
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if self.viewControllers.count > 1 {
return true
}
return false
}
}
extension UIView {
static func isRightToLeft() -> Bool {
return UIView.appearance().semanticContentAttribute == .forceRightToLeft
}
}
Resources:
Original question:
Slide gesture with custom back button freezes the root view controller
The answer is used for the solution:
https://stackoverflow.com/a/27683249/1138498
Other solution which may work better (But it's in Objective-C):
https://stackoverflow.com/a/28364126/1138498

Click rootViewController through viewController

I'm trying to add TopView in my application, it will be the same for each views. I do it like this
let vcTopMenu = storyboard?.instantiateViewControllerWithIdentifier("TopMenu")
let win:UIWindow = UIApplication.sharedApplication().delegate!.window!!
win.rootViewController = vcTopMenu
win.makeKeyAndVisible()
But when I add other viewControllers (I do it transparent) I can see buttons of TopView, but I can't click on it. It's a code from TopView
override func viewDidLoad()
{
super.viewDidLoad()
print("loaded")
}
#IBAction func btn(sender: AnyObject)
{
print("do something")
}
I see "loaded", but clicking doesn't work, how can I click through view? Thanks!
If I understand your question correctly, you're placing a translucent/transparent UIView on top of another UIView with a button you want to press?
The topmost UIView by default receives the touches. More on this here.
It's not a very standard/practical way to do things, but if you absolutely must, check out this answer: https://stackoverflow.com/a/4010809/4396258

Cannot set searchBar as firstResponder

I have a searchBar I'm setting in a tableviewcontroller. i've referenced this similar question UISearchBar cannot become first responder after UITableView did re-appear but am still unable to set it as first responder.
In .h file:
#property (strong, nonatomic) UISearchController *searchController;
#property (strong, nonatomic) IBOutlet UISearchBar *searchBar;
In view didload:
self.searchController = [[UISearchController alloc]initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.hidesNavigationBarDuringPresentation = NO;
self.searchController.searchBar.frame = CGRectMake(self.searchController.searchBar.frame.origin.x, self.searchController.searchBar.frame.origin.y, self.searchController.searchBar.frame.size.width, 44.0);
self.tableView.tableHeaderView = self.searchController.searchBar;
And in viewDidAppear:
-(void)viewDidAppear:(BOOL)animated {
[self.searchController setActive:YES];
[self.searchController.searchBar becomeFirstResponder];
[super viewDidAppear:animated];
}
When I segue to the view the searchBar animates, but no keyboard appears.
I noticed this issue too. What seems to happen is the call to becomeFirstResponder is done when the searchController is still 'loading'. If you comment out becomeFirstResponder you notice that there is no difference. So we need a way to call becomeFirstResponder after the searchController is 'done' loading.
When I looked at various delegate methods I noticed there is a delegate method:
- (void)didPresentSearchController:(UISearchController *)searchController
This method is called right after the searchController has been presented. Then I make the call to becomeFirstResponder:
- (void)didPresentSearchController:(UISearchController *)searchController
{
[searchController.searchBar becomeFirstResponder];
}
This fixes the problem. You will notice that when the searchController is loaded, the searchbar now has focus.
The solution with
- (void)didPresentSearchController:(UISearchController *)searchController did not work, since this delegate method is called only when the user taps on the search bar...
However, this solution did work:
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSelector:#selector(showKeyboard) withObject:nil afterDelay:0.1];
}
- (void) showKeyboard
{
[self.searchController.searchBar becomeFirstResponder];
}
Swift 3
delay(0.1) { self.searchController.searchBar.becomeFirstResponder() }
func delay(_ delay: Double, closure: #escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
Well, I found the solution that is actually perfectly working for me.
Don't call [self.searchController setActive:YES]; before calling [self.searchController.searchBar becomeFirstResponder];
What's better, don't call [self.searchController setActive:YES]; at all.
Call only [self.searchController.searchBar becomeFirstResponder]; and the keyboard just pops out as it should, without any delay.
It seems to be somewhat like a bug and a lot of people are confirming it. For example, check here: When assigning focus via becomeFirstResponder to UISearchController's UISearchBar, the keyboard does not appear
Swift 4, iOS 11
It works for me
// 1.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
resultSearchController.isActive = true
}
// 2. ->> UISearchControllerDelegate
func didPresentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {
searchController.searchBar.becomeFirstResponder()
}
}
The function searchController.searchBar.becomeFirstResponder() must be called in the main thread and after searchController.active = true in the viewDidLoad method. Here're the full solution. It works on iOS 9.3
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
searchController.active = true
Async.main {
self.searchController.searchBar.becomeFirstResponder()
}
}
Very similar to other answers, but I had to access the main queue in the ViewDidAppear.
The SearchBarController can't be acted upon until the View appears and then can only be done so in the main queue for UI:
searchController.active = true // doubtful whether this is needed
dispatch_async(dispatch_get_main_queue(), {
self.searchController.searchBar.becomeFirstResponder()
});
Easy Swift3 variant:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = mySearchController.searchBar
mySearchController.searchResultsUpdater = self
mySearchController.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
DispatchQueue.main.async {
self.mySearchController.isActive = true
}
}
func presentSearchController(_ searchController: UISearchController) {
mySearchController.searchBar.becomeFirstResponder()
}
It works ;-)
Swift 5 solution:
override func viewDidLoad() {
super.viewDidLoad()
definesPresentationContext = true
searchController.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
}
extension ViewController: UISearchControllerDelegate {
func didPresentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {[weak self] in
self?.searchController.searchBar.becomeFirstResponder()
}
}
}
Xcode 11.4, Swift 5.2
If you just want the SearchBar to appear but not activate the TextField & keyboard. There is no need to dispatch it to the main thread.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
}
If you want the TextField to activate with the keyboard, then you do need to call it on the main thread. There is no need to make the SearchController active, this happens automatically.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async {
self.searchController.searchBar.becomeFirstResponder()
}
}
This may depend on how you configure your SearchController. This is how I configure mine:
// Defined in class
let searchController = UISearchController(searchResultsController: nil)
// Called in viewDidLoad
navigationItem.searchController = searchController
searchController.searchResultsUpdater = self
searchController.searchBar.scopeButtonTitles = ["A", "B"]
searchController.searchBar.delegate = self
searchController.obscuresBackgroundDuringPresentation = false
Alternative Approach
This answer looks at a number of issues related to this occurrence and explains the debugging logic used to isolate and determine the
specific cause of the problem in my case. Along the way, I share other
possibilities that might have worked in other situations and explain
why this works in my situation.
tldr; Look at the self.definesPresentationContext = true, the
isBeingDismissed, isBeingPresented, canBecomeFirstResponder, and
delegate assignment of the SearchController, the Searchbar,
SearchResultsUpdater and their delegate methods.
(Source of self.definesPresentationContext solution - See SO answer)
One thing to keep in mind is the context of how the SearchBar is being presented. Embedded in a toolbar, navigation bar, another UIView, as an input or input accessory view. All of which, I've found to have some impact on the timing and internal animation of the search bar as it is being presented or dismissed.
I've attempted all of the solutions presented and none of these worked until I reconsidered how I was trying to use the searchBar. In my case, I was pushing a controller (B) with a search controller from a controller (A) that already had an initial searchcontroller on it. I programmatically embedded each of the search controllers within the titleView of my navigation item when doing a pull refresh.
The answers suggesting adding searchbar.becomeFirstResponder() into the life cycle didn't make sense for my use-case since the view was fully loaded by the time I wanted to insert and display my search bar into the navigationItem. The logic also seemed confusing since the view controller lifecycle methods should already be operating on the main thread. Adding a delay to the presentation also seemed to be an interference with the internal operations of the display and animation used by the system.
I found that calling my function to toggle the insertion worked when I pushed the view controller from controllerA but the keyboard would not display properly when pushed from controllerB. The difference between these two situations was that controllerA was a static tableview controller and controllerB had a tableview controller that I had made searchable by adding its own search controller.
e.g. controllerA with searchbar segues to controllerB with searchbar
Using a number of breakpoints and examining the status of the searchController and the searchbar at different points I was able to determine that the searchController.canBecomeFirstResponder was returning false. I also found that I needed to set the SearchResultsUpdater to self and the delegates on both the searchController and the searchBar.
I finally noted that setting self.definesPresentationContext = true on controllerA was not allowing the keyboard to be displayed when I pushed controllerB onto the navigation stack. My solution was to move the self.definesPresentationContext = true to viewDidAppear on controllerA and in the prepare(for:sender:) method of controllerA I change it to self.definesPresentationContext = false when the destination is controllerB. This resolved the keyboard display issue in my case.
A word on animation ~ I've found that when assigning things to the navigationItem or navigationBar, the system has some built in timing and default animations. I avoid adding custom animation, code in moveToParent methods, or delayed presentations because unexpected behavior occurs in many cases.
Why this solution works
Apple documentation on definesPresentationContext indicates the default behavior and notes some situations where this context adjusts the behavior the controller assigned to manage the keyboard appearance. In my case controllerA was assgined to manage the presentation rather than controllerB, so I just changed that behavior by adjusting this value:
When using the currentContext or overCurrentContext style to present a
view controller, this property controls which existing view controller
in your view controller hierarchy is actually covered by the new
content. When a context-based presentation occurs, UIKit starts at the
presenting view controller and walks up the view controller hierarchy.
If it finds a view controller whose value for this property is true,
it asks that view controller to present the new view controller. If no
view controller defines the presentation context, UIKit asks the
window’s root view controller to handle the presentation. The default
value for this property is false. Some system-provided view
controllers, such as UINavigationController, change the default value
to true.
This is what it worked for me in Swift 4
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
DispatchQueue.main.async{
self.searchController.searchBar.becomeFirstResponder()
}
}
The answer is to call becomeFirstResponder in viewDidAppear.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchBar?.becomeFirstResponder()
}
Based on #mislovr's solution, the 0.1 delay was not long enough. Here is my updated code to that answer.
func presentSearchController() {
searchController.isActive = true
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak searchController] timer in
guard let searchController = searchController else {
timer.invalidate()
return
}
if searchController.searchBar.canBecomeFirstResponder {
searchController.searchBar.becomeFirstResponder()
timer.invalidate()
}
}
}
i fixed this as follows:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
}
func didPresentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {
searchController.searchBar.searchTextField.becomeFirstResponder()
}
}
What happens is as soon as the view appears you set your searchControllers 'isActive' property to true. This fires the delegate method called didPresentSearchController. when that fires it means that searchcontroller is visible and thus can become first responder.
I think there might be a cleaner solution. I found that the keyboard was sort of 'blipping' up and then back down when presented, and calling becomeFirstResponder in didPresentSearchController: was working, but the keyboard was coming in late, and the animation was a bit quirky.
Wrapping my reload data method with a check for presentation made everyone happy:
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
{
if (!searchController.isBeingPresented && !searchController.isBeingDismissed) {
[self.collectionView reloadData];
}
}
I found this by setting a breakpoint in resignFirstResponder. resignFirstResponder was being called from the reloadData, which in turn was called by updateSearchResultsForSearchController:. It turns out that updateSearchResultsForSearchController: is called during the presentation of the search controller. If you muck with the view hierarchy that the UISearchBar is in during this time, the search controller gets borked. My guess is that the reloadData call was causing the UICollectionReusableView header view to come out and go back into the view hierarchy and forcing the UISearchBar subview to resign first responder.
Another symptom I saw was the the search term was not resetting to the middle of the search bar on cancel, which caused it not to present properly on future clicks.
The solution of mixing #edwardmp and #mislovr answers kind of worked for me (keyboard pops out with a slight delay):
- (void)didPresentSearchController:(UISearchController *)searchController {
[self performSelector:#selector(showKeyboard) withObject:nil afterDelay:0.001];
}
- (void) showKeyboard {
[self.searchController.searchBar becomeFirstResponder];
}
I battled with this for a while but got it working by:
Initializing the searchController in viewDidLoad()
Setting active = true in viewDidAppear()
This triggers didPresentSearchController() in the UISearchControllerDelegate extension.
Setting searchBar.becomeFirstResponder() in didPresentSearchController()
Here's the full example, it uses Google Maps Autocomplete.
class myViewController: UIViewController {
// MARK: Variables
var resultsViewController: GMSAutocompleteResultsViewController?
var searchController: UISearchController?
var resultView: UITextView?
// MARK: Outlets
#IBOutlet var myView: UIView!
// MARK: View Methods
override func viewDidLoad() {
super.viewDidLoad()
resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self
searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.delegate = self
searchController?.searchResultsUpdater = resultsViewController
searchController?.searchBar.prompt = "Search for a Place"
searchController?.searchBar.placeholder = "place name"
searchController?.searchBar.text = ""
searchController?.searchBar.sizeToFit()
searchController?.searchBar.returnKeyType = .Next
searchController?.searchBar.setShowsCancelButton(true, animated: false)
myView.addSubview((searchController?.searchBar)!)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
searchController?.active = true
}
// MARK: GMSAutocompleteResultsViewControllerDelegate Extension
extension myViewController: GMSAutocompleteResultsViewControllerDelegate {
func resultsController(resultsController: GMSAutocompleteResultsViewController,
didAutocompleteWithPlace place: GMSPlace) {
searchController?.active = false
// Do something with the selected place.
print("Place name: ", place.name)
print("Place address: ", place.formattedAddress)
print("Place attributions: ", place.attributions)
}
func resultsController(resultsController: GMSAutocompleteResultsViewController,
didFailAutocompleteWithError error: NSError){
// TODO: handle the error.
print("Error: ", error.description)
}
// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictionsForResultsController(resultsController: GMSAutocompleteResultsViewController) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictionsForResultsController(resultsController: GMSAutocompleteResultsViewController) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
}
extension myViewController: UISearchControllerDelegate {
func didPresentSearchController(searchController: UISearchController) {
self.searchController?.searchBar.becomeFirstResponder()
}
}
}
In Objective C:
- (void)viewDidLoad {
[super viewDidLoad];
// Setup your SearchViewController here...
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Will show Cancel button in White colour
[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:#[[UISearchBar class]]] setTintColor:[UIColor whiteColor]];
// searchController.active = YES; // This is not necessary
// set SearchBar first responder inside Main Queue block
dispatch_async(dispatch_get_main_queue(), ^{
[self->searchController.searchBar becomeFirstResponder];
});
}
Swift 5
class ViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Only after viewDidAppear searchController can be activated
searchController?.isActive = true
}
}
extension ViewController: UISearchControllerDelegate {
// didPresentSearchController not work for me
func presentSearchController(_ searchController: UISearchController) {
searchController.searchBar.becomeFirstResponder()
}
}
To me, there’s a quite big lag when using viewDidAppear. It can be better to use becomeFirstResponder asynchronously in viewDidLoad (tested with iOS 10, Swift 3):
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
searchController.searchBar.becomeFirstResponder()
}
}
This is what it worked for me in Swift 3.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.perform(#selector(self.showKeyboard), with: nil, afterDelay: 0.1)
}
func showKeyboard() {
self.searchController.searchBar.becomeFirstResponder()
}
Define searchBar.
SearchBar textfield "searchField" choice.
viewDidLoad or viewWillAppear call code.
#IBOutlet weak var searchBar: UISearchBar!
let textFieldUISearchBar = searchBar.value(forKey: "searchField") as? UITextField
textFieldUISearchBar?.becomeFirstResponder()

UINavigationController Interactive Pop Gesture Not Working?

So I have a navigation controller in my built for iOS 7 app. The titleView is visible, as well as the back button and navigation bar its self. For some reason, the interactive pop gesture (swipe from the left edge) isn't working. Nothing happens. When I log the gesture, it is not nil. Is there anything special I have to do to enable this functionality? What could cause it not to work?
I have found that when using custom back buttons, the interactive pop gesture stops working (my take is that Apple cannot foresee how your custom back button will behave, so they disable the gesture).
To fix this, as other mentioned before, you can set the interactivePopGestureRecognizer.delegate property to nil.
In Swift, this can easily be done across your entire application by adding an extension for UINavigationController like this:
extension UINavigationController {
override public func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = nil
}
}
Updated answer
Seems like setting the delegate to nil causes the app UI to freeze in some scenarios (eg. when the user swipes left or right on the top view controller of the navigation stack).
Because gestureRecognizerShouldBegin delegate method cannot be handled in an extension, subclassing UINavigationController seems like the best solution:
class NavigationController: UINavigationController, UIGestureRecognizerDelegate {
/// Custom back buttons disable the interactive pop animation
/// To enable it back we set the recognizer to `self`
override func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
Eh, looks like I just had to set the gesture delegate and implement the following:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
Look at this response and comments. All you have to do is set your navigation controller's interactive pop gesture recognizer's delegate to nil:
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
Setting it to a casted self to id<UIGestureRecognizerDelegate> also works because all methods in the protocol are optional, but I think setting the delegate to nil is more appropriate in this case.
My answer is based on Eneko's answer but uses only an extension on UINavigationController and works in Swift 5:
extension UINavigationController: UIGestureRecognizerDelegate {
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
You can put this line in the viewDidLoad method.
self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
If you feel you have tried all solutions and stretching your head then you're at the right place.
Goto simulator > Window > Enable Show Device Bezels
Now tried to simulate swipe to back gesture.
The more worked out answer was both Aaron and lojals
First Customise the Navigation controller and then put this code in the class
In ViewDidload put this line:
self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
And in class write this function
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES;}
Maybe someone may find this helpful.
If you want to hide the navigation bar but use normal swipe gestures to go back and other navigation controller features, you should use: (navigationBar)
self.navigationController?.navigationBar.isHidden = true
If you want to disable navigation bar (hide navigation bar, disable swipe for back) but want to push viewcontroller you should use: (isNavigationBarHidden)
self.navigationController?.isNavigationBarHidden = true
Update 7-DEC-2018:
Recommended way:
In case that your first controller use hidden navigation bar, but next childs use navigation bar, when you come back to base view controller you will see a black bar in transition in place of navigation bar. This will be fixed very easy if you use in first viewcontroller(parent):
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}
In Swift 4, I have a UITableView inside my view controller, I solved this issue with:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.interactivePopGestureRecognizer?.delegate=nil
}
Generically add interactive pop gesture to the whole app.
XCODE: 9.0, Swift: 4.0
Preferably create UINavigationController in AppDelegate.swift
Create a navigation controller
// I created a global variable, however not necessarily you will be doing this way
var nvc: UINavigationController!
implement UIGestureRecognizerDelegate
class AppDelegate: UIResponder, UIApplicationDelegate, UIGestureRecognizerDelegate {
Instantiat UINavigationController in application didFinishLaunchingWithOptions function
nvc=UINavigationController()
// For interactive pop gesture
nvc.navigationBar.isHidden=true
nvc?.interactivePopGestureRecognizer?.delegate=self
Extra step, add controller to navigation controller in application didFinishLaunchingWithOptions function
window=UIWindow()
window?.rootViewController=nvc
window?.makeKeyAndVisible()
// BaseViewController is sample controller i created with xib
nvc.pushViewController(BaseViewController(), animated: true)
Implement gusture recognizer, add below code to AppDelegate.swift
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Note: See other post in this section for the difference between
self.navigationController?.navigationBar.isHidden=true
And
self.navigationController?.isNavigationBarHidden = true

Resources