UISearchController - Objective C to Swift Issue - ios

I'm trying to subclass UISearchController so I can add a custom UISearchBar. I found a way to do this in Objective-C, but I'm struggling to do it in Swift. Here are the 2 files that accomplish this in Objective-C:
CustomSearchController.h
#interface CustomSearchController : UISearchController <UISearchBarDelegate>
#end
CustomSearchController.m
#import "CustomSearchController.h"
#import "CustomSearchBar.h"
#implementation CustomSearchController
{
UISearchBar *_searchBar;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(UISearchBar *)searchBar {
if (_searchBar == nil) {
_searchBar = [[CustomSearchBar alloc] initWithFrame:CGRectZero];
_searchBar.delegate = self; // different from table search by apple where delegate was set to view controller where the UISearchController was instantiated or in our case where CustomSearchController was instantiated.
}
return _searchBar;
}
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if ([searchBar.text length] > 0) {
self.active = true;
} else {
self.active = false;
}
}
/*
Since CustomSearchController is the delegate of the search bar we must implement the UISearchBarDelegate method.
*/
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
NSLog(#"Became first responder");
[searchBar resignFirstResponder];
}
#end
The issue I'm running into is specifically with this getter:
-(UISearchBar *)searchBar {
if (_searchBar == nil) {
_searchBar = [[CustomSearchBar alloc] initWithFrame:CGRectZero];
_searchBar.delegate = self; // different from table search by apple where delegate was set to view controller where the UISearchController was instantiated or in our case where CustomSearchController was instantiated.
}
return _searchBar;
}
In Swift I believe I would be have to do something like this:
var customSearchBar: CustomSearchBar?
override var searchBar: UISearchBar {
get {
if customSearchBar == nil {
customSearchBar = CustomSearchBar()
customSearchBar?.delegate = self
}
return customSearchBar!
}
}
But is this the best way to do something like this?

Try this:
lazy var customSearchBar: CustomSearchBar = {
[unowned self] in
let result = CustomSearchBar(frame:CGRectZero)
result.delegate = self
return result
}()
override var searchBar: UISearchBar {
get {
return customSearchBar
}
}
The usage of lazy takes care of initializing the CustomSearchBar instance only when first accessed. Although I'm not sure that you really need that for what you are trying to accomplish.

Related

SWRevealViewController - Prevent Interacting With The Back View

When using SWRevealViewController, I do not want to let the user interact with the view that opened the side menu. Once the side menu opens, I want to just be able to interact with that menu view and not the other one.
Any help?
well I has been working with/on SWRevealViewController for a while
so what you need is add your frontViewController as SWRevealViewControllerDelegate and then implementing this function
func revealController(revealController: SWRevealViewController!, willMoveToPosition position: FrontViewPosition)
you will be notified when frontViewController go to left or to front position
this is the Swift code
in your frontViewController you need to add
class FrontViewController: UIViewController, SWRevealViewControllerDelegate
{
override func viewDidLoad() {
super.viewDidLoad()
self.revealViewController().delegate = self;
}
//YOUR CODE//
func revealController(revealController: SWRevealViewController!, willMoveToPosition position: FrontViewPosition) {
if(position == FrontViewPosition.Left)
{
self.view.userInteractionEnabled = true;
self.navigationController?.navigationBar.userInteractionEnabled = true;
}else
{
self.view.userInteractionEnabled = false;
self.navigationController?.navigationBar.userInteractionEnabled = false;
}
}
//EDITED
This is the Objective C code
class FrontViewController: UIViewController <SWRevealViewControllerDelegate>
in the viewDidLoad you need to add
- (void)viewDidLoad
{
[super viewDidLoad];
self.revealViewController.delegate = self;
}
//YOUR CODE//
- (void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position
{
if(position == FrontViewPositionLeft)
{
self.view.userInteractionEnabled = NO;
self.navigationController.navigationBar.userInteractionEnabled = NO;
}else{
self.view.userInteractionEnabled = YES;
self.navigationController.navigationBar.userInteractionEnabled = YES;
}
}
I hope this help you

How to disable delaysContentTouches in UITableViewController? [duplicate]

I've looked at a ton of posts on similar things, but none of them quite match or fix this issue. Since iOS 7, whenever I add a UIButton to a UITableViewCell or even to the footerview it works "fine", meaning it receives the target action, but it doesn't show the little highlight that normally happens as you tap a UIButton. It makes the UI look funky not showing the button react to touch.
I'm pretty sure this counts as a bug in iOS7, but has anyone found a solution or could help me find one :)
Edit:
I forgot to mention that it will highlight if I long hold on the button, but not a quick tap like it does if just added to a standard view.
Code:
Creating the button:
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.titleLabel.font = [UIFont systemFontOfSize:14];
button.titleLabel.textColor = [UIColor blueColor];
[button setTitle:#"Testing" forState:UIControlStateNormal];
[button addTarget:self action:#selector(buttonPressed:) forControlEvents: UIControlEventTouchDown];
button.frame = CGRectMake(0, 0, self.view.frame.size.width/2, 40);
Things I've Tested:
//Removing gesture recognizers on UITableView in case they were getting in the way.
for (UIGestureRecognizer *recognizer in self.tableView.gestureRecognizers) {
recognizer.enabled = NO;
}
//Removing gestures from the Cell
for (UIGestureRecognizer *recognizer in self.contentView.gestureRecognizers) {
recognizer.enabled = NO;
}
//This shows the little light touch, but this isn't the desired look
button.showsTouchWhenHighlighted = YES;
In that tableview you just add this property.
tableview.delaysContentTouches = NO;
And add in cellForRowAtIndexPath after you initiate the cell you just add below code. The structure of the cell is apparently different in iOS 6 and iOS 7.
iOS 7 we have one control UITableViewCellScrollView In between UITableViewCell and content View.
for (id obj in cell.subviews)
{
if ([NSStringFromClass([obj class]) isEqualToString:#"UITableViewCellScrollView"])
{
UIScrollView *scroll = (UIScrollView *) obj;
scroll.delaysContentTouches = NO;
break;
}
}
Since iOS 8 we need to apply the same technique to UITableView subviews (table contains a hidden UITableViewWrapperView scroll view). There is no need iterate UITableViewCell subviews anymore.
for (UIView *currentView in tableView.subviews) {
if ([currentView isKindOfClass:[UIScrollView class]]) {
((UIScrollView *)currentView).delaysContentTouches = NO;
break;
}
}
This answer should be linked with this question.
I tried to add this to the accepted answer but it never went through. This is a much safer way of turning off the cells delaysContentTouches property as it does not look for a specific class, but rather anything that responds to the selector.
In Cell:
for (id obj in self.subviews) {
if ([obj respondsToSelector:#selector(setDelaysContentTouches:)]) {
[obj setDelaysContentTouches:NO];
}
}
In TableView:
self.tableView.delaysContentTouches = NO;
For a solution that works in both iOS7 and iOS8, create a custom UITableView subclass and custom UITableViewCell subclass.
Use this sample UITableView's initWithFrame:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// iterate over all the UITableView's subviews
for (id view in self.subviews)
{
// looking for a UITableViewWrapperView
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewWrapperView"])
{
// this test is necessary for safety and because a "UITableViewWrapperView" is NOT a UIScrollView in iOS7
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
return self;
}
Use this sample UITableViewCell's initWithStyle:reuseIdentifier:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
// iterate over all the UITableViewCell's subviews
for (id view in self.subviews)
{
// looking for a UITableViewCellScrollView
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewCellScrollView"])
{
// this test is here for safety only, also there is no UITableViewCellScrollView in iOS8
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
return self;
}
What I did to solve the problem was a category of UIButton using the following code :
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[NSOperationQueue.mainQueue addOperationWithBlock:^{ self.highlighted = YES; }];
}
- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self performSelector:#selector(setDefault) withObject:nil afterDelay:0.1];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self performSelector:#selector(setDefault) withObject:nil afterDelay:0.1];
}
- (void)setDefault
{
[NSOperationQueue.mainQueue addOperationWithBlock:^{ self.highlighted = NO; }];
}
the button reacts correctly when I press on it in a UITableViewCell, and my UITableView behaves normally as the delaysContentTouches isn't forced.
Here's Roman B's answer in Swift 2:
for view in tableView.subviews {
if view is UIScrollView {
(view as? UIScrollView)!.delaysContentTouches = false
break
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
for (id view in self.tableView.subviews)
{
// looking for a UITableViewWrapperView
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewWrapperView"])
{
// this test is necessary for safety and because a "UITableViewWrapperView" is NOT a UIScrollView in iOS7
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
I was having similar issues with a text-only UIButton in a UITableViewCell not highlighting upon touch. What fixed it for me was changing the buttonType from Custom back to System.
Setting delaysContentTouches to NO did the trick for the image-only UIButton in the same UITableViewCell.
self.tableView.delaysContentTouches = NO;
This is a Swift version of Raphaƫl Pinto's answer above. Don't forget to upvote him too :)
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in self.highlighted = true }
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
self.setDefault()
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
self.setDefault()
}
}
func setDefault() {
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in self.highlighted = false }
}
Solution in Swift, iOS8 only (needs the extra work on each of the cells for iOS7):
//
// NoDelayTableView.swift
// DivineBiblePhone
//
// Created by Chris Hulbert on 30/03/2015.
// Copyright (c) 2015 Chris Hulbert. All rights reserved.
//
// This solves the delayed-tap issue on buttons on cells.
import UIKit
class NoDelayTableView: UITableView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
delaysContentTouches = false
// This solves the iOS8 delayed-tap issue.
// http://stackoverflow.com/questions/19256996/uibutton-not-showing-highlight-on-tap-in-ios7
for view in subviews {
if let scroll = view as? UIScrollView {
scroll.delaysContentTouches = false
}
}
}
override func touchesShouldCancelInContentView(view: UIView!) -> Bool {
// So that if you tap and drag, it cancels the tap.
return true
}
}
To use, all you have to do is change the class to NoDelayTableView in your storyboard.
I can confirm that in iOS8, buttons placed inside a contentView in a cell now highlight instantly.
Slightly modified version of Chris Harrison's answer. Swift 2.3:
class HighlightButton: UIButton {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
NSOperationQueue.mainQueue().addOperationWithBlock { _ in self.highlighted = true }
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
setDefault()
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
setDefault()
}
private func setDefault() {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
NSOperationQueue.mainQueue().addOperationWithBlock { _ in self.highlighted = false }
}
}
}
The accepted answer did not work at some "taps" for me .
Finally I add the bellow code in a uibutton category(/subclass),and it works a hundred percent.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.backgroundColor = [UIColor greenColor];
[UIView animateWithDuration:0.05 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.backgroundColor = [UIColor clearColor];
} completion:^(BOOL finished)
{
}];
[super touchesBegan:touches withEvent:event];
}
I wrote a category extension on UITableViewCell to make this issue simple to address. It does basically the same thing as the accepted answer except I walk up the view hierarchy (as opposed to down) from the UITableViewCell contentView.
I considered a fully "automagic" solution that would make all cells added to a UITableView set their delaysContentTouches state to match the owning UITableView's delaysContentTouches state. To make this work I'd have to either swizzle UITableView, or require the developer to use a UITableView subclass. Not wanting to require either I settled on this solution which I feel is simpler and more flexible.
Category extension and sample harness here:
https://github.com/TomSwift/UITableViewCell-TS_delaysContentTouches
It's dead-simple to use:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// using static cells from storyboard...
UITableViewCell* cell = [super tableView: tableView cellForRowAtIndexPath: indexPath];
cell.ts_delaysContentTouches = NO;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
Here's the code for the category:
#interface UITableViewCell (TS_delaysContentTouches)
#property (nonatomic, assign) BOOL ts_delaysContentTouches;
#end
#implementation UITableViewCell (TS_delaysContentTouches)
- (UIScrollView*) ts_scrollView
{
id sv = self.contentView.superview;
while ( ![sv isKindOfClass: [UIScrollView class]] && sv != self )
{
sv = [sv superview];
}
return sv == self ? nil : sv;
}
- (void) setTs_delaysContentTouches:(BOOL)delaysContentTouches
{
[self willChangeValueForKey: #"ts_delaysContentTouches"];
[[self ts_scrollView] setDelaysContentTouches: delaysContentTouches];
[self didChangeValueForKey: #"ts_delaysContentTouches"];
}
- (BOOL) ts_delaysContentTouches
{
return [[self ts_scrollView] delaysContentTouches];
}
#end
Since objc is dynamic, and scrollView is the only class that responds to delaysContentTouches, this should work for both ios 7 and 8 (put it somewhere early in your tableViewController, like awakeFromNib):
for (id view in self.tableView.subviews)
{
if ([view respondsToSelector:#selector(delaysContentTouches)]) {
UIScrollView *scrollView = (UIScrollView *)view;
scrollView.delaysContentTouches = NO;
break;
}
}
You may also have to turn off "delaysContentTouches" in your storyboard or nib by selecting the table inside your viewController. BTW, this might not work on ios 7 if you're using a tableView inside a viewController, at least I couldn't get it to work.
That solution for me doesn't work, I fixed subclassing TableView and implementing these two methods
- (instancetype)initWithCoder:(NSCoder *)coder{
self = [super initWithCoder:coder];
if (self) {
for (id obj in self.subviews) {
if ([obj respondsToSelector:#selector(setDelaysContentTouches:)]){
[obj performSelector:#selector(setDelaysContentTouches:) withObject:NO];
}
}
}
return self;
}
- (BOOL)delaysContentTouches{
return NO;
}
Solution in Swift for iOS 7 and 8:
First I wrote a utility function:
class func classNameAsString(obj: AnyObject) -> String {
return _stdlib_getDemangledTypeName(obj).componentsSeparatedByString(".").last!
}
then I subclass UITableView and implement this:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for view in self.subviews {
if (Utility.classNameAsString(view) == "UITableViewWrapperView") {
if view.isKindOfClass(UIScrollView) {
var scroll = (view as UIScrollView)
scroll.delaysContentTouches = false
}
break
}
}
}
I also subclass UITableViewCell and implement this:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for view in self.subviews {
if (Utility.classNameAsString(view) == "UITableViewCellScrollView") {
if view.isKindOfClass(UIScrollView) {
var scroll = (view as UIScrollView)
scroll.delaysContentTouches = false
}
}
}
}
In my case the init(coder:) will run. Please put debug point in your init functions to know which init function will run, then using the code above to make it work.
Hope to help someone.
In Swift 3 this UIView extension can be used on the UITableViewCell. Preferably in the cellForRowAt method.
func removeTouchDelayForSubviews() {
for subview in subviews {
if let scrollView = subview as? UIScrollView {
scrollView.delaysContentTouches = false
} else {
subview.removeTouchDelayForSubviews()
}
}
}

How can I overiride UIcollectioView Delegate and datasource method in its own class

I had made a class of UICollectionView and I want to override its behaviour,so that whenever I want to show collectionView I make Class of UIView i.e UICollectionView.
Currently I'm doing it like this....
#import "CustomCollectionView.h"
#implementation CustomCollectionView
- (id)initWithFrame:(CGRect)frame {
self = [super init];
if (self) {
NSLog(#"");
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
}
return self;
}
- (void)customInit {
}
So in customInit method i want to set the size of Cell and want to override datasource method too....

Make UISearchController search bar automatically active

I've implemented a UISearchController with its search bar in a navigatiom bar and I would like to make the search bar active when the view is loaded. When I say active, I mean that the keyboard appears and the user can type his/her search without tap the search bar.
I initialised the UISearchController with the following code:
- (void)viewDidLoad
{
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
[self.searchController setSearchResultsUpdater:self];
[self.searchController setDimsBackgroundDuringPresentation:NO];
[self.searchController setHidesNavigationBarDuringPresentation:NO];
[self.navigationItem setTitleView:self.searchController.searchBar];
[self setDefinesPresentationContext:YES];
[self.searchController.searchBar setDelegate:self];
[self.searchController.searchBar setShowsCancelButton:YES];
[self.searchController.searchBar setPlaceholder:#"City or Airfield"];
[self.searchController.searchBar sizeToFit];
}
I've tried to make my search controller active, call [self.searchController.searchBar becomeFirstResponder] and directly call searchBarSearchButtonClicked but nothing works.
Try calling
[self.searchController setActive:YES]
before
[self.searchController.searchBar becomeFirstResponder]
If the above is not working, try something like this:
- (void)viewDidLoad {
[super viewDidLoad];
...
[self initializeSearchController];
....
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.searchController setActive:YES];
[self.searchController.searchBar becomeFirstResponder];
}
- (void)initializeSearchController {
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.delegate = self;
self.searchController.searchBar.delegate = self;
[self.searchController.searchBar sizeToFit];
[self.tableView setTableHeaderView:self.searchController.searchBar];
self.definesPresentationContext = YES;
}
To activate the search bar:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.searchController setActive:YES];
}
"becomeFistResponder" can be called to make the keyboard appear only when UISearchController is loaded .
- (void)didPresentSearchController:(UISearchController *)searchController
{
[searchController.searchBar becomeFirstResponder];
}
Besides doing what the other users suggested, I also did the following, and it worked:
searchController.definesPresentationContext = YES;
For those looking for a solution in 2022
I have tried the solution provided here with no luck.
So, here are the three approaches in swift that each seems to work.
Using delegate, make Searchcontroller active in the main queue.
class ViewController: UIViewController {
private lazy var searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
searchController.delegate = self
navigationItem.searchController = searchController
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// If this is not called in main queue, the searchbar is not being active
DispatchQueue.main.async {
self.searchController.isActive = true
}
}
}
extension ViewController: UISearchControllerDelegate {
func didPresentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {
searchController.searchBar.becomeFirstResponder()
}
}
}
Making the searchbar firstResponder after some delay.
class ViewController: UIViewController {
private lazy var searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.searchController = searchController
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
delay(0.1) { self.searchController.searchBar.becomeFirstResponder() }
}
func delay(_ delay: Double, closure: #escaping ()-> Void) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
}
Or just making the searchbar firstResponder in main queue.
class ViewController: UIViewController {
private lazy var searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.searchController = searchController
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async { // must call from main thread
self.searchController.searchBar.becomeFirstResponder()
}
}
}

SWRevealViewController: Remove interaction on frontview when rearview is revealed

I need to disable user interaction on front view when rear view is revealed. Found some others asking the same thing but can't really understand where or how to implement the code that I've seen.
Ex: I found this code from link,
- (void)revealController:(SWRevealViewController *)revealController
willMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
- (void)revealController:(SWRevealViewController *)revealController
didMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
Also found few other links
Link1
Link2
Link3
I have this code, but not really sure about the correct place to insert this code. I've tried adding it in my front/rear views and also in the SWRevealViewController method with no success
Appreciate if someone can point me in the right direction.
I've recently come up with a solution that I wanted to share
(sorry if it's 2 months late).
To disable user interaction on the Front View while the Menu is open, I added the following codes on my MenuViewController:
on viewWillAppear:
[self.revealViewController.frontViewController.view setUserInteractionEnabled:NO];
and on viewWillDisappear:
[self.revealViewController.frontViewController.view setUserInteractionEnabled:YES];
This will disable all user interactions on the Front View Controller, which means that the slide / tap gestures to CLOSE the menu will also be DISABLED.
Now, I have created a ParentViewController and made all the view controllers (the menu items) a subclass of it.
on my viewDidLoad, I put the following codes:
SWRevealViewController *revealController = [self revealViewController];
[revealController panGestureRecognizer];
[revealController tapGestureRecognizer];
If you run your app at this point, it would appear that the Tap Gesture works (a tap on the Front View will close the Menu), but NOT the Pan Gesture. I'm not sure why this is so, but in order to enable the slide gesture to CLOSE your menu, add the following code in your MenuViewController:
on viewWillAppear:
[self.revealViewController.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
To summarize, here's what you need:
On your MenuViewController:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.revealViewController.frontViewController.view setUserInteractionEnabled:NO];
[self.revealViewController.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.revealViewController.frontViewController.view setUserInteractionEnabled:YES];
}
And on your menu items' view controller (you can make a ParentViewController for all of them):
-(void)viewDidLoad {
[super viewDidLoad];
SWRevealViewController *revealController = [self revealViewController];
[revealController panGestureRecognizer];
[revealController tapGestureRecognizer];
}
Hope this helps!
I have used another approach to achieve the same outcome not sure if it helps.
Assign SWRevealViewControllerDelegate to AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
SWRevealViewController* reveal = (SWRevealViewController*)self.window.rootViewController;
reveal.delegate = self;
// other bootstrapping code
}
and then in the delegate method -(void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position as below:
-(void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position
{
if(position == FrontViewPositionLeft){
[revealController.frontViewController.view setUserInteractionEnabled:YES];
[revealController.frontViewController.revealViewController tapGestureRecognizer];
}else{
[revealController.frontViewController.view setUserInteractionEnabled:NO];
}
}
UPDATED: added this line [revealController.frontViewController.revealViewController tapGestureRecognizer] to close the revealed controller when tap on frontviewcontroller
Swift version to #hardluckbaby answer:
In MenuViewController(Rear view controller):
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.revealViewController().frontViewController.view.userInteractionEnabled = false
self.revealViewController().view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.revealViewController().frontViewController.view.userInteractionEnabled = true
}
In FrontViewController(You can make a ParentViewController for all of your front view controllers as #hardluckbaby said):
override func viewDidLoad() {
super.viewDidLoad()
if let revealController = self.revealViewController() {
revealController.panGestureRecognizer()
revealController.tapGestureRecognizer()
}
}
As John Explained: Although these solutions do work, I don't think any of them address the original question, which is actually quite simple:
There are 2 steps involved:
Add the following methods to your FrontViewController.m:
(void)revealController:(SWRevealViewController *)revealController
willMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
(void)revealController:(SWRevealViewController *)revealController
didMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
Make your front view controller be a delegate of SWRevealViewController in the FrontViewController.h file:
(e.g. #interface HomeViewController : UIViewController )
where my FrontViewController was named HomeViewController
and also in the FrontViewController.m file with the following on ViewDidLoad:
self.revealViewController.delegate = self;
Problem solved! Much easier than creating parent classes, etc.
This will help you solve the user interactions for the FrontView controller lovely I would just add the following change taken from Xun's response also above and you will solve both the user interactions and the hide menu when user taps FrontViewController.
- (void)revealController:(SWRevealViewController *)revealController
willMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
- (void)revealController:(SWRevealViewController *)revealController
didMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
//Hides the menu when user taps FrontViewController
[revealController.frontViewController.revealViewController tapGestureRecognizer];
}
}
Swift 3.0 simple and fast method.
Frontviewcontoller code here...
override func viewDidLoad() {
super.viewDidLoad()
if self.revealViewController() != nil {
let rewel:SWRevealViewController = revealViewController()
rewel.panGestureRecognizer()
rewel.tapGestureRecognizer()
}
}
SideDrowerviewcontoller code here...
override func viewWillAppear(_ animated: Bool) {
let rewel = self.revealViewController()
rewel?.frontViewController.view.isUserInteractionEnabled = false
rewel?.frontViewController.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
override func viewWillDisappear(_ animated: Bool) {
let rewel = self.revealViewController()
rewel?.frontViewController.view.isUserInteractionEnabled = true
}
Add a subview to front view when rear view is open.
In viewWillAppear method of your menu items controller, Just create an overlay button on the front view and set action to revealToggle: of revealViewController
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
overlayView = [UIButton buttonWithType:UIButtonTypeCustom];
overlayView.frame = self.revealViewController.frontViewController.view.bounds;
overlayView.backgroundColor = [UIColor colorWithWhite:0.5 alpha:0.8];
overlayView.tag = 999;
[overlayView addTarget:self.revealViewController action:#selector(revealToggle:) forControlEvents:UIControlEventTouchUpInside];
[overlayView addTarget:self.revealViewController action:#selector(revealToggle:) forControlEvents:UIControlEventTouchDragOutside];
[self.revealViewController.frontViewController.view addSubview:overlayView];
}
In revealTogglle method remove the overlay button if any:
- (void)revealToggleAnimated:(BOOL)animated
{
UIButton *overlayView = (UIButton*)[self.view viewWithTag:999];
if (overlayView) {
[overlayView removeFromSuperview];
overlayView = nil;
}
// rest of the code...
}
Although these solutions do work, I don't think any of them address the original question, which is actually quite simple:
There are 2 steps involved:
1) Add the following methods to your FrontViewController.m:
- (void)revealController:(SWRevealViewController *)revealController
willMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
- (void)revealController:(SWRevealViewController *)revealController
didMoveToPosition:(FrontViewPosition)position {
if(position == FrontViewPositionLeft) {
self.view.userInteractionEnabled = YES;
} else {
self.view.userInteractionEnabled = NO;
}
}
2) Make your front view controller be a delegate of SWRevealViewController in the FrontViewController.h file:
(e.g. #interface HomeViewController : UIViewController <SWRevealViewControllerDelegate>)
where my FrontViewController was named HomeViewController
and also in the FrontViewController.m file with the following on ViewDidLoad:
self.revealViewController.delegate = self;
Problem solved! Much easier than creating parent classes, etc.
Another way is to have an overlay view when the rear view is revealed. You can use this updated library https://github.com/NSRover/SWRevealViewController and make sure you include shouldUseFrontViewOverlay = true when the rear view is revealed.
class SideMenuViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.revealViewController().delegate = self
}
}
extension SideMenuViewController: SWRevealViewControllerDelegate {
func revealController(revealController: SWRevealViewController!, willMoveToPosition position: FrontViewPosition) {
if position == .Left {
revealController.frontViewController.view.userInteractionEnabled = true
}
if position == .Right {
revealController.frontViewController.view.userInteractionEnabled = false
}
}
}
On MenuTableViewController/ Rear VC, add SWRevealViewControllerDelegate.
override func viewDidLoad() {
super.viewDidLoad()
self.revealViewController().delegate = self
if self.revealViewController() != nil {
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
}
Add this delegate method.
func revealController(revealController: SWRevealViewController!, didMoveToPosition position: FrontViewPosition) {
if(position.rawValue == 4)
{
//move to rear
self.revealViewController().frontViewController.view.userInteractionEnabled = false
}
else if (position.rawValue == 3)
{
//move to front - dashboard VC
self.revealViewController().frontViewController.view.userInteractionEnabled = true
}
}
func revealController(revealController: SWRevealViewController!, willMoveToPosition position: FrontViewPosition) {
//will perform the same function as the above delegate method.
}
Consider following solution, works perfect
private let DimmingViewTag = 10001
extension UIViewController: SWRevealViewControllerDelegate {
func removeInteractionFromFrontViewController() {
revealViewController().delegate = self
view.addGestureRecognizer(revealViewController().panGestureRecognizer())
}
//MARK: - SWRevealViewControllerDelegate
public func revealController(revealController: SWRevealViewController!, didMoveToPosition position: FrontViewPosition) {
if case .Right = position {
let dimmingView = UIView(frame: view.frame)
dimmingView.tag = DimmingViewTag
view.addSubview(dimmingView)
view.bringSubviewToFront(dimmingView)
} else {
view.viewWithTag(DimmingViewTag)?.removeFromSuperview()
}
}
}
Simple usage in UIViewController:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
removeInteractionFromFrontViewController()
}
Addition to hardluckbaby answer.
If you run your app at this point, it would appear that the Tap
Gesture works (a tap on the Front View will close the Menu), but NOT
the Pan Gesture. I'm not sure why this is so, but in order to enable
the slide gesture to CLOSE your menu, add the following code in your
MenuViewController:
on viewWillAppear:
[self.revealViewController.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
It adds some undesired behavior, e.g. pan gesture will close rear view when starts on it.
Default pan gesture may not work if you add it to your own view somewhere, something like on viewDidLoad of your front view controller:
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
Remove such lines and this should works as expected, for pan and tap gestures
SWRevealViewController *revealController = [self revealViewController];
[revealController panGestureRecognizer];
[revealController tapGestureRecognizer];
I was using the viewWillAppear and viewWillDisappear functions but as I have subviews for almost every item in the side menu I had. My issue was that I had a input field active (keyboard displaying) and accessed the side menu. In the root menu the keyboard hid but after I entered a submenu keyboard showed up again. To solve this I changed the approach to enable and disable the interaction in revealController like this:
- (void)revealController:(SWRevealViewController *)revealController
didMoveToPosition:(FrontViewPosition)position {
if (position == FrontViewPositionRight) {
[self.revealViewController.frontViewController.view setUserInteractionEnabled:NO];
} else if (position == FrontViewPositionLeft) {
[self.revealViewController.frontViewController.view setUserInteractionEnabled:YES];
}
}
Firstly just set your delegate :
self.revealViewController.delegate = self;
and the delegate method are given below :
- (void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position
{
static NSInteger tagLockView = 123456789;
if (revealController.frontViewPosition == FrontViewPositionRight)
{
UIView *lockView = [self.view viewWithTag:tagLockView];
[UIView animateWithDuration:0.3 animations:^{
lockView.alpha = 0;
} completion:^(BOOL finished) {
[lockView removeFromSuperview];
}];
}
else if (revealController.frontViewPosition == FrontViewPositionLeft)
{
UIView *lockView = [[UIView alloc] initWithFrame:self.view.bounds];
lockView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
lockView.tag = tagLockView;
lockView.backgroundColor = [UIColor blackColor];
lockView.alpha = 0;
[lockView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self.revealViewController action:#selector(revealToggle:)]];
[self.view addSubview:lockView];
[UIView animateWithDuration:0.3 animations:^{
lockView.alpha = 0.5;
}];
}
}

Resources