I have this code which captures a barcode; it works fine, except on an iPad, if you can't/don't hold the iPad steady, it "tries until the cows come home" if you get my drift. I want to add a "cancel" button or figure out some way to cancel the method so I don't have to kill the app and start it again. Here is my code:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
CGRect highlightViewRect = CGRectZero;
AVMetadataMachineReadableCodeObject *barCodeObject;
NSString *detectionString = nil;
NSArray *barCodeTypes = #[AVMetadataObjectTypeEAN13Code];
for (AVMetadataObject *metadata in metadataObjects) {
for (NSString *type in barCodeTypes) {
if ([metadata.type isEqualToString:type])
{
barCodeObject = (AVMetadataMachineReadableCodeObject *)[_prevLayer transformedMetadataObjectForMetadataObject:(AVMetadataMachineReadableCodeObject *)metadata];
highlightViewRect = barCodeObject.bounds;
detectionString = [(AVMetadataMachineReadableCodeObject *)metadata stringValue];
break;
}
}
if (detectionString != nil) {
_label.text = detectionString;
oISBNField.text = detectionString; // move detectionString to ISBN textbox
[_session stopRunning];
[_prevLayer removeFromSuperlayer];
[_label removeFromSuperview];
break;
}
else
_label.text = #"(none)";
}
}
Can someone please give me some help on this? I would really, really appreciate it! :D
Just create a cancel button and add it to the UIViewController's view.
When the button is pressed, stop the capture session and dismiss the presented view controller.
- (void)cancelButtonPressed:(id)sender {
[self.captureSession stopRunning]; //stop the capture session
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; // dismiss the current view controller
}
Here i have some code that checks if the iphone has a camera flash. And then check if my switch is on by using this code:
if(switch1.on){
}
The light turns on fine without this code and i have also tried this code as well:
if(!(switch1.on)){
}
and that successfully turns my camera flash on but also turns it on even when the switch is set to off
Here is my full code:
-(IBAction)torchon:(id)sender{
AVCaptureDevice *flashlight = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if ([flashlight isTorchAvailable] & [flashlight isTorchModeSupported:AVCaptureTorchModeOn]) {
BOOL success = [flashlight lockForConfiguration:Nil];
if(success){
if(switch1.on){ //// or (!(switch1.on))
on.hidden = YES;
[UIScreen mainScreen].brightness = 1.0;
[flashlight setTorchMode:AVCaptureTorchModeOn];
[flashlight unlockForConfiguration];
}
}
}
}
any help would be appreciated
**EDIT:**
Here is the code where i set the switch to no using ibactions when the value of the switch has changed. This code works successfully:
-(IBAction)switch1{
if (switch1.on) {
switch1.on = YES;
switch2.on = NO;
} else{
switch2.on = YES;
switch1.on = NO;
}
}
-(IBAction)switch2{
if (switch2.on) {
switch2.on = YES;
switch1.on = NO;
} else{
switch1.on = YES;
switch2.on = NO;
}
}
Answer
First add a bool:
bool yes;
then set that bool in the viewdidload
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
yes = YES;
}
After that set it to YES when the ibaction is called and set it to NO once the switch is turned off:
-(IBAction)switch1{
if (swich1.on) {
swich1.on = YES;
swich2.on = NO;
yes = YES;
}
}
-(IBAction)switch2{
if (swich2.on) {
swich2.on = YES;
swich1.on = NO;
yes = NO;
}
}
lastly where it checks if(switch1.on){} you need to change it to if(yes==YES){} so together that if statement looks like this:
-(IBAction)torchon:(id)sender{
AVCaptureDevice *flashlight = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if ([flashlight isTorchAvailable] & [flashlight isTorchModeSupported:AVCaptureTorchModeOn]) {
BOOL success = [flashlight lockForConfiguration:Nil];
if(success){
if(yes==YES){
on.hidden = YES;
[UIScreen mainScreen].brightness = 1.0;
[flashlight setTorchMode:AVCaptureTorchModeOn];
[flashlight unlockForConfiguration];
}
}
}
}
The UISwitch has another getter for the on property.
#property(nonatomic, getter=isOn) BOOL on;
It may be better to use .isOn instead of .on for getting the value.
Also, some of your code can be simplified:
-(IBAction)switch1{
[switch2 setOn:!switch1.isOn];
}
-(IBAction)switch2{
[switch1 setOn:!switch2.isOn];
}
It can get even better if you use the sender of the action in your methods.
It seems that your IBAction methods have a similar form as the getter for the switch. It is possible that this could lead to undesired side effects. Refactor your methods to a single method, more in line with good programming practice:
-(IBAction)switchDidChange:(UISwitch*)aSwitch {
UISwitch* otherSwitch = (aSwitch == switch1)? switch2 : switch1;
[otherSwitch setOn:!aSwitch.on animated:YES];
}
Also, in your code the on property does not really seem to influence what is switched on or off. Include this conditionality.
I am trying to figure out how I can get notified when the keyboard changes. What I am trying to do is add a DONE button to keyboard of type 4 & 5 (NumberPad and PhonePad), everything is working fine, except when I transition from a TextField using a Default KB type, The notification that the KeyboardDidAppear isn't being fired.
Here is what I got:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
}
Then I added a Property for the current KB type and the current TextField being edited:
#pragma mark - Delegate Methods
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
self.currentKBType = textField.keyboardType;
self.curTextField = textField;
return YES;
}
I then make a decision on whether or not to add that DONE button based on the current KB type:
- (void)keyboardDidShow:(NSNotification *)note {
if (self.currentKBType == 4 || self.currentKBType == 5) {
[self addButtonToKeyboard];
}
}
Problem is that the notification fires when the keyboard is displayed, but not when it changes (transitions from one TextField to another that specifies a different KB type.
Any suggestions? Am I missing something?
Got this figured out. Took a little logic, but it works flawlessly. Here is what I did:
Added private properties for:
#property (nonatomic) UIKeyboardType currentKBType;
#property(nonatomic,strong) UITextField *curTextField;
#property(nonatomic,strong) UIButton *doneButton;
#property(nonatomic) BOOL doneButtonDisplayed;
Then added the following logic in the both the TextField delegate method:
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
self.currentKBType = textField.keyboardType;
if (textField.keyboardType == 4 || textField.keyboardType == 5) {
if (!doneButtonDisplayed) {
[self addButtonToKeyboard];
}
} else {
if (doneButtonDisplayed) {
[self removeButtonFromKeyboard];
}
}
self.curTextField = textField;
return YES;
}
And in the KeyboardDidShowNotification that I signed the VC up for in the viewDidLoad:
- (void)keyboardDidShow:(NSNotification *)note {
if (self.currentKBType == 4 || self.currentKBType == 5) {
if (!doneButtonDisplayed) {
[self addButtonToKeyboard];
}
} else {
if (doneButtonDisplayed) {
[self removeButtonFromKeyboard];
}
}
}
And the two methods referenced in these methods:
- (void)addButtonToKeyboard {
// create custom button
doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
doneButton.frame = CGRectMake(0, 163, 106, 53);
doneButton.adjustsImageWhenHighlighted = NO;
[doneButton setImage:[UIImage imageNamed:#"DoneNormal.png"] forState:UIControlStateNormal];
[doneButton setImage:[UIImage imageNamed:#"DoneHL.png"] forState:UIControlStateHighlighted];
[doneButton addTarget:self action:#selector(resignKeyboard) forControlEvents:UIControlEventTouchUpInside];
// locate keyboard view
if ([[[UIApplication sharedApplication] windows] count] > 1) {
UIWindow* tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
UIView* keyboard;
for(int i=0; i<[tempWindow.subviews count]; i++) {
keyboard = [tempWindow.subviews objectAtIndex:i];
// keyboard found, add the button
if([[keyboard description] hasPrefix:#"<UIPeripheralHost"] == YES) {
[keyboard addSubview:doneButton];
self.doneButtonDisplayed = YES;
}
}
}
}
- (void)removeButtonFromKeyboard {
[doneButton removeFromSuperview];
self.doneButtonDisplayed = NO;
}
And finally, the resignKeyboard method that is called whenever the Done button is touched:
-(void)resignKeyboard {
[self.curTextField resignFirstResponder];
self.doneButtonDisplayed = NO;
}
This will add that Done button whenever a NumberPad or PhonePad type keyboard is displayed and remove it (only when it has been added) from the other keyboard types.
Tested and works great.
I am presenting a modal view controller as a form sheet and dismissing it when the cancel button, which is a bar button item, is clicked. I need to dismiss it when I tap on outside of that view. Please help me with a reference. Note: my modal view controller is presented with a navigation controller.
#cli_hlt, #Bill Brasky thanks for your answer. I need to dismiss it when tap occurs outside of the modal view which is a form sheet. I am pasting my code below.
-(void)gridView:(AQGridView *)gridView didSelectItemAtIndex:(NSUInteger)index
{
if(adminMode)
{
CHEditEmployeeViewController *editVC = [[CHEditEmployeeViewController alloc] initWithNibName:#"CHEditEmployeeViewController" bundle:nil];
editVC.delegate = self;
editVC.pickedEmployee = employee;
editVC.edit = TRUE;
editVC.delegate = self;
UINavigationController *navigationController = [[UINavigationController alloc]initWithRootViewController:editVC];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:navigationController animated:YES];
return;
} //the above code is from the view controller which presents the modal view. Please look at the below code too which is from my modal view controller. Please guide me in a proper way. -(void)tapGestureRecognizer {
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view addGestureRecognizer:recognizer];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
[self dismissModalViewControllerAnimated:YES];
[self.view.window removeGestureRecognizer:sender];
}
}
}
I know this is an old question but this IS possible, despite of what the "right" answer says. Since this was the first result when I was looking for this I decided to elaborate:
This is how you do it:
You need to add a property to the View Controller from where you want to present modally, in my case "tapBehindGesture".
then in viewDidAppear
if(!tapBehindGesture) {
tapBehindGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapBehindDetected:)];
[tapBehindGesture setNumberOfTapsRequired:1];
[tapBehindGesture setCancelsTouchesInView:NO]; //So the user can still interact with controls in the modal view
}
[self.view.window addGestureRecognizer:tapBehindGesture];
And Here is the implementation for tapBehindDetected
- (void)tapBehindDetected:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
//(edited) not working for ios8 above
//CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
CGPoint location = [sender locationInView: self.presentingViewController.view];
//Convert tap location into the local view's coordinate system. If outside, dismiss the view.
if (![self.presentedViewController.view pointInside:[self.presentedViewController.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
if(self.presentedViewController) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
}
Just remember to remove tapBehindGesture from view.window on viewWillDisappear to avoid triggering handleTapBehind in an unallocated object.
I solved iOS 8 issue by adding delegate to gesture recognizer
[taprecognizer setDelegate:self];
with these responses
#pragma mark - UIGestureRecognizer Delegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return YES;
}
that works for me with iOS 8 GM
Swift 4 version that works in both portrait and landscape - no swapping of x, y required.
class TapBehindModalViewController: UIViewController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if (self.tapOutsideRecognizer == nil) {
self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapBehind))
self.tapOutsideRecognizer.numberOfTapsRequired = 1
self.tapOutsideRecognizer.cancelsTouchesInView = false
self.tapOutsideRecognizer.delegate = self
self.view.window?.addGestureRecognizer(self.tapOutsideRecognizer)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if(self.tapOutsideRecognizer != nil) {
self.view.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
self.tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
// MARK: - Gesture methods to dismiss this with tap outside
#objc func handleTapBehind(sender: UITapGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.ended) {
let location: CGPoint = sender.location(in: self.view)
if (!self.view.point(inside: location, with: nil)) {
self.view.window?.removeGestureRecognizer(sender)
self.close(sender: sender)
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
As far as I can tell none of the answer seem to be working right away in every condition.
My solution (either inherit from it or paste it in):
#interface MyViewController () <UIGestureRecognizerDelegate>
#property (strong, nonatomic) UITapGestureRecognizer *tapOutsideRecognizer;
#end
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (!self.tapOutsideRecognizer) {
self.tapOutsideRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
self.tapOutsideRecognizer.numberOfTapsRequired = 1;
self.tapOutsideRecognizer.cancelsTouchesInView = NO;
self.tapOutsideRecognizer.delegate = self;
[self.view.window addGestureRecognizer:self.tapOutsideRecognizer];
}
}
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// to avoid nasty crashes
if (self.tapOutsideRecognizer) {
[self.view.window removeGestureRecognizer:self.tapOutsideRecognizer];
self.tapOutsideRecognizer = nil;
}
}
#pragma mark - Actions
- (IBAction)close:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self close:sender];
}
}
}
#pragma mark - Gesture Recognizer
// because of iOS8
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
Here is my version that works for iOS 7 and iOS 8 and does not require conditional swapping of coordinates:
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:self.view];
if (![self.view pointInside:location withEvent:nil]) {
[self.view.window removeGestureRecognizer:self.recognizer];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
For iOS 8, you must both implement the UIGestureRecognizer per Martino's answer, and swap the (x,y) coordinates of the tapped location when in landscape orientation. Not sure if this is due to an iOS 8 bug.
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// add gesture recognizer to window
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
recognizer.delegate = self;
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
// passing nil gives us coordinates in the window
CGPoint location = [sender locationInView:nil];
// swap (x,y) on iOS 8 in landscape
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
location = CGPointMake(location.y, location.x);
}
}
// convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil]) {
// remove the recognizer first so it's view.window is valid
[self.view.window removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
#pragma mark - UIGestureRecognizer Delegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return YES;
}
Based on Bart van Kuik's answer and NavAutoDismiss and other great snippets here.
class DismissableNavigationController: UINavigationController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if tapOutsideRecognizer == nil {
tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(DismissableNavigationController.handleTapBehind))
tapOutsideRecognizer.numberOfTapsRequired = 1
tapOutsideRecognizer.cancelsTouchesInView = false
tapOutsideRecognizer.delegate = self
view.window?.addGestureRecognizer(tapOutsideRecognizer)
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if tapOutsideRecognizer != nil {
view.window?.removeGestureRecognizer(tapOutsideRecognizer)
tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}
func handleTapBehind(sender: UITapGestureRecognizer) {
if sender.state == UIGestureRecognizerState.Ended {
var location: CGPoint = sender.locationInView(nil)
if UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation) {
location = CGPoint(x: location.y, y: location.x)
}
if !view.pointInside(view.convertPoint(location, fromView: view.window), withEvent: nil) {
view.window?.removeGestureRecognizer(sender)
close(sender)
}
}
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Usage:
let vc = MyViewController()
let nc = DismissableNavigationController(rootViewController: vc)
nc.modalPresentationStyle = UIModalPresentationStyle.FormSheet
presentViewController(nc, animated: true, completion: nil)
#yershuachu's answer, in Swift 2:
class ModalParentViewController: UIViewController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if(self.tapOutsideRecognizer == nil) {
self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: "handleTapBehind:")
self.tapOutsideRecognizer.numberOfTapsRequired = 1
self.tapOutsideRecognizer.cancelsTouchesInView = false
self.tapOutsideRecognizer.delegate = self
self.view.window?.addGestureRecognizer(self.tapOutsideRecognizer)
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if(self.tapOutsideRecognizer != nil) {
self.view.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
self.tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func handleTapBehind(sender: UITapGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.Ended) {
let location: CGPoint = sender.locationInView(nil)
if (!self.view.pointInside(self.view.convertPoint(location, fromView: self.view.window), withEvent: nil)) {
self.view.window?.removeGestureRecognizer(sender)
self.close(sender)
}
}
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Swift 3
class ModalParentViewController: UIViewController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if(self.tapOutsideRecognizer == nil) {
self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapBehind))
self.tapOutsideRecognizer.numberOfTapsRequired = 1
self.tapOutsideRecognizer.cancelsTouchesInView = false
self.tapOutsideRecognizer.delegate = self
appDelegate.window?.addGestureRecognizer(self.tapOutsideRecognizer)
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if(self.tapOutsideRecognizer != nil) {
appDelegate.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
self.tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
// MARK: - Gesture methods to dismiss this with tap outside
func handleTapBehind(sender: UITapGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.ended) {
let location: CGPoint = sender.location(in: nil)
if (!self.view.point(inside: self.view.convert(location, from: self.view.window), with: nil)) {
self.view.window?.removeGestureRecognizer(sender)
self.close(sender: sender)
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Ah ok. So I'm afraid thats not quite possible using the presentModalViewController: method. The whole idea of a "modal" view/window/message box/etc. pp. is that the user cannot do anything else than processing whatever the view/window/message box/etc. pp. wants him/her to do.
What you want to do instead is not present a modal view controller, but rather load and show your form view controller the regular way. Note in your master controller that the form is just showing e.g. with a BOOL variable and then handle there any taps that might occur. If your form is showing, dismiss it.
I use it in this form without any problems neither on iOS 7.1 nor iOS 8.3.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.view.window addGestureRecognizer:self.tapBehindGesture];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.view.window removeGestureRecognizer:self.tapBehindGesture];
}
- (UITapGestureRecognizer*)tapBehindGesture
{
if (_tapBehindGesture == nil)
{
_tapBehindGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapBehindRecognized:)];
_tapBehindGesture.numberOfTapsRequired = 1;
_tapBehindGesture.cancelsTouchesInView = NO;
_tapBehindGesture.delegate = self;
}
return _tapBehindGesture;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
FYI, on iOS 13 form sheets now have a standard dismiss gesture - they go away when you touch anywhere inside the modal and swipe it down (set the new isModalInPresentation property to false if you want to prevent that).
I made a navigationController that auto dismiss for iPad
https://github.com/curciosobrinho/NavAutoDismiss
It is just the same code as above, working on iOS 8.
So, all the credits go to the people above.
I just did it to be more generic and easier to use.
You just need to copy BOTH files to your project
Import the header file (.h)
And use your viewcontroller (that you want to show) as the rootViewController.
How to use it:
//Import the .h file
#import "NavDismissViewController.h"
//Instanciate your view controller (the view you want to present)
YourViewController * yourVC = [YourViewController new];
//Instanciate the NavDismissViewController with your view controller as the rootViewController
NavDismissViewController *nav = [[NavDismissViewController alloc] initWithRootViewController:yourVC];
//if you want to change the navigationBar translucent behaviour
[nav.navigationBar setTranslucent:NO];
//Choose the Modal style
nav.modalPresentationStyle=UIModalPresentationFormSheet;
//present your controller
[self presentViewController:nav animated:YES completion:nil];
//Done
Based on some of the questions and with some improvements and updates:
- (void)tapOutsideDetected:(UITapGestureRecognizer *)sender {
if (#available(iOS 13, *)) {} else {
UIView *aView = self.navigationController ? self.navigationController.view : self.view;
CGPoint location = [sender locationInView: aView];
if (![aView pointInside:location withEvent:nil]) {
[self removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
Where:
- (void) removeGestureRecognizer:(UIGestureRecognizer *) gesture {
if (self.recognizer) {
UIView *aView = self.recognizer.view;
if (aView) {
[aView removeGestureRecognizer:self.recognizer];
}
}
}
In this case, the UITapGestureRecognizer has been applied to the UIViewController
Issue
Description
The following code results in load method of TTModel not being called. I've stepped it through the debugger, as well as stepping through the TTCatalog application. The only difference between the two that I can see, is that when the catalog sets it's DataSource in the createModel method of the controller, TTModel's load method gets called, whereas mine does not.
About the Code
I've commented the specific areas of code, to tell what they should be doing and what problem is happening, but I included all of it for completion's sake.
You should look at specifically
PositionsController's createModel method
PositionsList's load method
Those are the areas where the issue is, and would be the best place to start.
Code
PositionsController : TTTableViewController
- (id)initWIthNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
self.title = #"Positions";
self.variableHeightRows = NO;
self.navigationBarTintColor = [UIColor colorWithHexString:#"1F455E"];
}
return self;
}
//This method here should result in a call to the PositionsList load method
- (void)createModel
{
PositionsDataSource *ds = [[PositionsDataSource alloc] init];
self.dataSource = ds;
[ds release];
}
- (void)loadView
{
[super loadView];
self.view = [[[UIView alloc] initWithFrame:TTApplicationFrame()] autorelease];
self.tableView = [[[UITableView alloc] initWithFrame:TTApplicationFrame() style:UITableViewStylePlain] autorelease];
self.tableView.backgroundColor = [UIColor colorWithHexString:#"E2E7ED"];
self.tableView.separatorColor = [UIColor whiteColor];
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
//self.tableView.delegate = self;
[self.view addSubview:self.tableView];
}
//Override UITableViewDelegate creation method, so we can add drag to refresh
- (id<TTTableViewDelegate>) createDelegate {
TTTableViewDragRefreshDelegate *delegate = [[TTTableViewDragRefreshDelegate alloc] initWithController:self];
return [delegate autorelease];
}
PositionsDataSource : TTListDataSource
#implementation PositionsDataSource
#synthesize positionsList = _positionsList;
-(id)init
{
if (self = [super init])
{
_positionsList = [[PositionsList alloc] init];
self.model = _positionsList;
}
return self;
}
-(void)dealloc
{
TT_RELEASE_SAFELY(_positionsList);
[super dealloc];
}
-(void)tableViewDidLoadModel:(UITableView*)tableView
{
self.items = [NSMutableArray array];
}
-(id<TTModel>)model
{
return _positionsList;
}
#end
PositionsList : NSObject <TTModel>
#implementation PositionsList
//============================================================
//NSObject
- (id)init
{
if (self = [super init])
{
_delegates = nil;
loaded = NO;
client = [[Client alloc] init];
}
return self;
}
- (void)dealloc
{
TT_RELEASE_SAFELY(_delegates);
[client dealloc];
[super dealloc];
}
//==============================================================
//TTModel
- (NSMutableArray*)delegates
{
if (!_delegates)
{
_delegates = TTCreateNonRetainingArray();
}
return _delegates;
}
-(BOOL)isLoadingMore
{
return NO;
}
-(BOOL)isOutdated
{
return NO;
}
-(BOOL)isLoaded
{
return loaded;
}
-(BOOL)isEmpty
{
//return !_positions.count;
return NO;
}
-(BOOL)isLoading
{
return YES;
}
-(void)cancel
{
}
//This method is never called, why is that?
-(void)load:(TTURLRequestCachePolicy)cachePolicy more:(BOOL)more
{
//This method is not getting called
//When the PositionsController calls self.datasource, load should be called,
//however it isn't.
[_delegates perform:#selector(modelDidStartLoad:) withObject:self];
[client writeToServer:self dataToSend:#""];
}
-(void)invalidate:(BOOL)erase
{
}
#end
Short answer: return NO instead of YES for isLoading in your PositionList.
For a longer explanation:
If you dig through the Three20 source, you'll find that setting the dataSource on a view controller sets the model, refreshing the model and potentially calling load. Here is the code called when the TTModelViewController refreshes:
- (void)refresh {
_flags.isViewInvalid = YES;
_flags.isModelDidRefreshInvalid = YES;
BOOL loading = self.model.isLoading;
BOOL loaded = self.model.isLoaded;
if (!loading && !loaded && [self shouldLoad]) {
[self.model load:TTURLRequestCachePolicyDefault more:NO];
} else if (!loading && loaded && [self shouldReload]) {
[self.model load:TTURLRequestCachePolicyNetwork more:NO];
} else if (!loading && [self shouldLoadMore]) {
[self.model load:TTURLRequestCachePolicyDefault more:YES];
} else {
_flags.isModelDidLoadInvalid = YES;
if (_isViewAppearing) {
[self updateView];
}
}
}
Your PositionList object is returning YES for isLoading and NO for isLoaded. This means that Three20 thinks your model is loading when it isn't. You may also need to implement shouldLoad if it doesn't return YES by default.