UITableView push segue triggered multiple times - ios

I came across a strange bug in my app:
The setup
A simple Master-Detail app, iPhone style (ie. no split view, no popover, just a navigation controller, a table view controller, and a view controller).
The bug
Touch a "background" part of the table view (the darker grey parts on my screenshot) like a section header or footer.
While keeping your finger on the screen, touch a cell multiple times.
Release all fingers. The "detail" view will pushed normally, but when touching the back button, you will find that the detail view was stacked as many times as you touched the cell at step 2.
You can also touch multiple cells at step 2 and their destination views will be stacked in the correct order :)
Reproduce it
I was able to reproduce the bug with a clean, freshly created app, and on the last release of the Twitter app for iPhone (by touching the "Loading" label with finger #1 and touching a tweet multiple times).
However, I could not trigger the same behaviour in the Settings app, under the "General" tab (which is a grouped table view).
The bug was reproduced on iOS 6.0 and 6.1. I don't have devices with older versions to test.
Question
Is this a known trick when creating navigation/table view based apps and if so is there a solution to prevent this (weird) behavior ? Or is this an iOS bug (and if so, is it already known from Apple) ?

A possible stop-gap measure you could use is to implement
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
And use a boolean flag or something to indicate that you are currently trying to execute that segue. ex:
BOOL doingSegue = NO;
-(void) viewWillAppear
{
doingSegue = NO;
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
if ( [identifier isEqualToString:#"MySegueIdentifier"] )
{
if ( doingSegue )
{
return NO;
}
else
{
doingSegue = YES;
return YES;
}
}
return YES;
}
Swift Version
var doingSegue = false
override func viewWillAppear(_ animated: Bool) {
doingSegue = false
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "MySegueIdentifier" {
if doingSegue {
return false
}
else {
doingSegue = true
return true
}
}
return true
}

This is fixed by Apple in iOS 7.
For prior versions of the OS, Dan F's answer should do the trick.

Related

Safari View Controller Swipe Left to dismiss goes black

I have an iOS app which holds a wkWebView. This wkWebView has links which can pop open an instance of SafariViewController. When SafariViewController is launched and you swipe right to dismiss sometimes it works but sometimes it goes black.
I've tried multiple variations of setting interactivePopGestureRecognizer.enabled to false. Also setting its delegate to nil.
I have the delegate methods which have break points and none of them get hit.
I want to disable this feature entirely.
It seem likes a bug in iOS. You can try to workaround i.e
Add SafariViewController into the rootController of NavigationController
Then present the NavigationController instead of SafariViewController.
https://forums.developer.apple.com/thread/29048
https://www.cocoanetics.com/2015/10/swiping-away-sfsafariviewcontroller/
Here's a temporary workaround I'm using to disable the edge swipe gesture. It doesn't seem to be a problem as long as the Done button is used to dismiss.
let viewController = SFSafariViewController(URL: url)
presentViewController(viewController, animated: true) {
for view in viewController.view.subviews {
if let recognisers = view.gestureRecognizers {
for gestureRecogniser in recognisers where gestureRecogniser is UIScreenEdgePanGestureRecognizer {
gestureRecogniser.enabled = false
}
}
}
}
OC:
for (UIView * view in safari.view.subviews) {
NSArray<__kindof UIGestureRecognizer *> * array = view.gestureRecognizers;
if (array.count) {
for (UIScreenEdgePanGestureRecognizer * sepgr in array) {
sepgr.enabled = NO;
}
}
}
SFSafariViewController in iOS 9.2 | Apple Developer Forums

forceTouchCapability returning nil

I am trying to incorporate some 3D touch into an application and I've run into a weird issue where the forceTouchCapability check is returning nil on viewDidLoad but not in viewWillAppear/viewDidAppear.
I'm aware that this is only available on iOS 9+ so I've added checks to verify that the traitCollection property on the view controller responds to forceTouchCapability as in the following:
- (void)loadView {
self.view = [[MyView alloc] init];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Checking the force touch availability here
if ([self.traitCollection respondsToSelector:#selector(forceTouchCapability)] &&
self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
// This won't get called because forceTouchCapability is returning nil
// which corresponds to UIForceTouchCapabilityUnknown
[self registerForPreviewingWithDelegate:self sourceView:self.view];
}
}
In LLDB with a breakpoint at the if statement, entering po [self.traitCollection forceTouchCapability] returns nil which corresponds to UIForceTouchCapabilityUnknown. However, the traitCollection itself is not nil.
According to the documentation for UIForceTouchCapabilityUnknown:
UIForceTouchCapabilityUnknown: The availability of 3D Touch is unknown. For example, if you create a view but have not yet added it to your app’s view hierarchy, the view’s trait collection has this value.
Has the view not been added to the hierarchy by this point?
I'm curious if anyone has run into this issue before and how to work around this? I would like to avoid adding this in the viewDidAppear as this can get called quite a bit.
If it helps, I'm running this on a 6S on iOS 9.1 with Xcode 7.2
The view hasn't been added to the View Hierarchy yet. You can see this easily by checking for a superview in the debug console
(lldb) po self.view.superview
nil
If that's what you're seeing, the view hasn't been added to a hierarchy yet: so you have to put your check elsewhere.
This is kind of confusing because in Apple's ViewControllerPreview sample app it's in viewDidLoad. But it really should in traitCollectionDidChange:, because then you're sure that the view has been added to the app's hierarchy.
This is the code I use (works on iOS 8, if you don't need to support that feel free to move the outer conditional).
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if ([self.traitCollection respondsToSelector:#selector(forceTouchCapability)]) {
if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
// retain the context to avoid registering more than once
if (!self.previewingContext) {
self.previewingContext = [self registerForPreviewingWithDelegate:self sourceView:self.view];
}
} else {
[self unregisterForPreviewingWithContext:self.previewingContext];
self.previewingContext = nil;
}
}
}
The added benefit to this is that your view will be registered/unregistered if the user changes their 3D Touch settings while the app is running.
I've also seen this issue, and found that the easiest way to check whether the device can support force touch or not is doing it via the screen instance. This kinda makes sense because the capability is a property of the screen. Doing it this way means you don't have to worry about the lifecycle of a viewcontroller or a view.
func canForceTouch() -> Bool
{
if iOS9OrHigher // pseudocode, a function that makes sure u only do this check on ios9 or higher
{
return UIScreen.mainScreen().traitCollection.forceTouchCapability == .Available
}
return false
}
Like what #bpapa said, Your view hasn't added to view hierarchy yet, But my solution is different little bit:
var token:dispatch_once_t = 0
override func viewDidAppear(animated: Bool) {
dispatch_once(&token) {
// Force Touch Checking
if #available(iOS 9.0, *) {
if self.traitCollection.forceTouchCapability == .Available {
self.registerForPreviewingWithDelegate(self, sourceView: self.view)
}
}
}
}

resignFirstResponder keyboard will NOT go away before segue

I am stumped and I hope someone can help.
I am calling the resign first responder method for all five of my text fields prior to a segue. The segue occurs, if the keyboard was visible prior to the segue, the keyboard remains no matter what I do. This did not happen in IOS6. It is only happening in IOS7.
Thank you so much in advance for your assistance.
Here is the scenario:
The user touches one text field at time to enter data. The keyboard has no problems changing from first responder from one field to the next and can be resigned from the DONE button without issues. The problem comes when the user touches a field that will be populated from the picker view. If the keyboard was visible from one of the previous text fields, it won't go away.
I have this code attempting to resignFirstResponder on the editingDidBegin action of two of the fields. I am using these two fields to hold numbers but I am filling them from a picker on the next view.
- (IBAction)txtRatioOrHullTypeTouched:(id)sender
{
// Hide the keyboard before the segue to the picker occurs.
[self.txtPitch resignFirstResponder];
[self.txtRPM resignFirstResponder];
[self.txtSlipOrSpeed resignFirstResponder];
[self.txtRatio resignFirstResponder];
[self.txtHullType resignFirstResponder];
segueToPicker = YES; // Raise flag indicating that this segue is to the picker.
[self performSegueWithIdentifier:#"toPicker" sender:sender];
}
I also put this same code in the viewWillDisappear as shown here:
- (void)viewWillDisappear:(BOOL)animated // Unchanged
{
// Hide the keyboard before the segue to the picker occurs.
[self.txtPitch resignFirstResponder];
[self.txtRPM resignFirstResponder];
[self.txtSlipOrSpeed resignFirstResponder];
[self.txtRatio resignFirstResponder];
[self.txtHullType resignFirstResponder];
[super viewWillDisappear:animated];
}
Both of these methods are on the initial view, ViewController.m file.
I ended up here removing the text field causing the problem and replacing them with buttons. No scenario I tried (dozens) got this code to work as expected in IOS7, even though it all worked flawlessly in IOS6.
I tried all of the above and it worked as long as i dismissed the controller with a button. The function that was called when pressing the button could call the TextField's resignFirstResponder() function and all was well.
However, when an edge swipe was performed to dismiss the controller the keyboard kept popping up the next time I showed it. In my code I reuse the same controller between views. This might not be wise but, it's snappy!
After trying everything the internet had written (well not really, but pretty close) about this I found that i could implement the TextField's textViewShouldBeginEditing() and return false between the ViewControllers ViewDidDisappear and ViewDidAppear. It's ha hack, but it did the trick when nothing else worked.
I hope this helps you guys!
Swift code:
In my ViewController
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
myTextField.allowEdit = true
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
myTextField.allowEdit = false
}
In my TextField class
class MyTextField: UIView, UITextFieldDelegate {
var allowEdit = true
func textFieldShouldBeginEditing(textView: UITextView) -> Bool {
return allowEdit
}
}
You can call endEditing: on the view controller with the text fields. Your viewWillDisappear: method will look like this:
- (void)viewWillDisappear:(BOOL)animated
{
[self.view endEditing:YES];
[super viewWillDisappear:animated];
}
Contributing my 2 cents worth. dismissing keyboard correctly on iOS 9.2, a minimalist sample, FYI.
...
#property (assign) BOOL isTransitioning;
...
-(void)viewWillAppear:(BOOL) animated {
self.isTransitioning = YES;
}
-(void)viewWillDisappear:(BOOL) animated {
self.isTransitioning = YES;
}
-(void)viewDidAppear:(BOOL) animated {
self.isTransitioning = NO;
}
-(void)viewDidDisappear:(BOOL) animated {
self.isTransitioning = NO;
}
-(BOOL) textViewShouldBeginEditing:(UITextView*) tv {
if (self.isTransitioning) {
return NO;
}
return YES;
}
I think due to the way you are leaving the view through a picker, without going through an exit, you need to include the following in your viewController:
- (BOOL) disablesAutomaticKeyboardDismissal
{
return NO;
}
Swift, 2017
override var disablesAutomaticKeyboardDismissal: Bool {
get { return false }
set { }
}
So it seems now that the text field that controls the keyboard will not allow resignation. I used the canResignFirstResponder query on that field and the result (boolean) was FALSE. I also noticed that i get a flashing cursor in the field even after the resignFirstResponder is called. – Larry J Oct 25 '13 at 23:32
I know this is old, but I had a similar issue and wanted to share what worked for me in case it might help anyone else:
After reading the above comment I found that moving [self.view endEditing:YES] from where I had it in textFieldDidBeginEditing to textFieldSHOULDBeginEditing did the trick for me. Now the keyboard is dismissing properly before my segue.
Taking Zaheer's comment into Swift this works very well for me.
view.endEditing(true)
This is a problem i have frequently. My best method to cope is creating a clear button under the keyboard and having that call a dismiss helper. Control the clear button by toggling its isHidden property. Tapping outside the keyboard will hit that clear button and call the dismiss helper. What it won't do is trigger your segue, the user will need to tap again to navigate out but that keyboard will be gone.
in viewDidLoad():
var clearButton: UIButton!
self.clearButton = UIButton(frame: self.view.frame)
self.clearButton.backgroundColor = .clear
self.clearButton.addTarget(self, action: #selector(self.dismissHelper(_:)), for: .touchUpInside)
self.view.addSubview(self.clearButton)
self.clearButton.isHidden = true
Then add the dismiss helper:
func dismissHelper(_ sender: UIButton?) {
self.clearButton.isHidden = true
view.endEditing(true)
}
func displayClearButton(){
print("display clear button, hidden = false")
self.clearButton.isHidden = false
}
then on your textfield add the target
self.textField.addTarget(self, action: #selector(self.displayClearButton), for: .editingDidBegin)

Discard segue during uitableview editing mode

When I change my uitableview to edit mode, i'd like the user to be able to select a cell without the segue taking place. The segue was linked in the storyboard. Is there a way to disable the segue during edit mode?
I can't disable interaction with the cell during edit more because I need to pick up on the editing control (insert button) press.
In your view controller, override:
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender{
return !self.isEditing;
}
Inside that method check if this is the correct segue, and verify the edit state. If the editing is on, return NO; otherwise, return YES.
Swift 3+:
Assuming you've inherited from UITableViewController:
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
return !tableView.isEditing
}
Note this will disable all segues while editing. If you have more than one and you want some to remain enabled in edit mode, apply the appropriate logic within this function to return true or false as appropriate.

Modal Dialog Does Not Dismiss Keyboard

I am running into an issue where the keyboard does not get dismissed when leaving a UITextField or UITextView in a UIModalPresentationFormSheet. In addition, I've created a large button to serve as the view's background so if the user taps outside the fields it gets triggered. I am using the same code in a regular view controller, and it works as expected. In the modal view controller it does nothing. Any suggestions would be appreciated.
- (BOOL)textFieldShouldReturn:(id)sender {
[titleTextField resignFirstResponder];
return YES;
}
- (BOOL)textViewShouldReturn:(id)sender {
[synopsisTextView resignFirstResponder];
return YES;
}
- (IBAction)textFieldDoneEditing:(id)sender {
[sender resignFirstResponder];
}
- (IBAction)textViewDoneEditing:(id)sender {
[sender resignFirstResponder];
}
- (IBAction)backgroundClick:(id)sender {
[titleTextField resignFirstResponder];
[synopsisTextView resignFirstResponder];
}
Overriding disablesAutomaticKeyboardDismissal to return NO as below fixed the same problem of mine. You should put this code to your view controller, from which you initiate the keyboard:
- (BOOL)disablesAutomaticKeyboardDismissal {
return NO;
}
Also, check this SO question if you want to get a detailed explanation.
For those having trouble with UINavigationController, I think there is a better solution than a category on UIViewController. We should change the behavior of UINavigationController to ask its topViewController (in my opinion, this is how all ViewController containers should handle this).
#implementation UINavigationController (DelegateAutomaticDismissKeyboard)
- (BOOL)disablesAutomaticKeyboardDismissal {
return [self.topViewController disablesAutomaticKeyboardDismissal];
}
If you're presenting a modal view with presentation style "form sheet", Apple apparently does not dismiss the keyboard, thinking that they don't want the keyboard to jump in and out where a user will be doing a lot of editing (i.e. "forms"). The fix would be to change presentation style or live with it.
If you implement the UITextFieldDelegate protocol you can inadvertently cause this behavior if you do text validation. If your validation codes returns false from textFieldShouldEndEditing when the text is invalid, the field can't relinquish it's firstResponder status and the keyboard will remain on screen in the next view.
More details at UITextField's keyboard won't dismiss. No, really
I solved this by resizing a UIModalPresentationPageSheet. See my answer here.
The disablesAutomaticKeyboardDismissal refused to work for me on iOS 7.
But... I managed to solve this issue by simply disabling the UITextFields on the screen.
My solution is described here.
This workaround even works on Modal UIViewControllers.
Yeah... it surprised me aswell !!
i have also facing same problem and also done everything but not thing works then i start thinking and get some result.
but this answer for those who want to dismiss keyboard on textfield click and then open pop up.
so all you need to call text field delegate
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if textField == self.myTxtFieldName{
self.view.endEditing(true) // keyboard hide code
// here you can call your model or pop up code and keyboard will dismiss and your pop up open
return false
}
return true
}
Sorry if this is not working for you
if there is other answer then please edit it
Thank you

Resources