Safari View Controller Swipe Left to dismiss goes black - ios

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

Related

ARSCNView freezes when presenting and dismissing UIViewController over it [duplicate]

In my ARKit app I am presenting a modal window. When I close the modal and go back to the ARSCNView then I find out that the session is paused due to this code:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
When I close the modal and go back to the ARKit camera view screen this code gets fired:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingSessionConfiguration()
// Run the view's session
sceneView.session.run(configuration)
}
But this code never resumes the session. The screen is completely frozen on the last image it read. Any ideas?
I update the viewDidAppear code to be the following. It is still stuck on the camera screen with image frozen.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingSessionConfiguration()
sceneView.session.delegate = self
if self.isPaused {
sceneView.session.run(sceneView.session.configuration!)
} else {
// Run the view's session
sceneView.session.run(configuration)
}
}
Not sure why your session isn't resuming, but... this generally isn't a situation you want to be in anyway.
Notice in the readme that ships with Apple's ARKit sample code (attached to the WWDC17 session on ARKit):
Avoid interrupting the AR experience. If the user transitions to another fullscreen UI in your app, the AR view might not be an expected state when coming back.
Use the popover presentation (even on iPhone) for auxiliary view controllers to keep the user in the AR experience while adjusting settings or making a modal selection. In this example, the SettingsViewController and VirtualObjectSelectionViewController classes use popover presentation.
To go into a bit more detail: if you pause the session, it won't be tracking the world while your user is away in a different fullscreen view controller. That means that when you resume, any virtual content placed in the scene won't be in the positions (relative to the camera) where you left it.
I don't know if the iOS 11 GM Seed or XCode 9 GM Seed versions fixed this today however I can successfully resume a paused ARSCNview with code as in the original question.
sceneView.session.run(sceneView.session.configuration!)
I get that you have chosen an answer, and that answer is what is recommended by apple, you can restart the AR Session. You can't unpause/resume the Session though, because the device stops it's tracking once you're out of your controller presenting the ARSceneView and will stop keeping track of the position of your device relative to the objects you've placed in the scene.
Anyway, I've managed to restart the session essentially by destroying all aspects of my session and rebuilding them them when my view reappears, or through a button press.
I'll post some sample code here. It's in Objective-C cause my project was written in that, but it might help future people with the same question.
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated]
[self setupScene];
[self setupSession];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self destroySession];
[self destroyScene];
}
- (void)setupScene {
// Setup the ARSCNViewDelegate - this gives us callbacks to handle new
// geometry creation
self.sceneView.delegate = self;
// A dictionary of all the current planes being rendered in the scene
self.planes = [NSMutableDictionary new];
// Contains a list of all the boxes rendered in the scene
self.boxes = [NSMutableArray new];
// Show statistics such as fps and timing information
self.sceneView.showsStatistics = YES;
self.sceneView.autoenablesDefaultLighting = YES;
SCNScene *scene = [SCNScene new];
[self.sceneView setScene:scene];
self.sceneView.scene.physicsWorld.contactDelegate = self;
}
- (void)setupSession {
// Create a session configuration
ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfiguration new];
//ARWorldTrackingSessionConfiguration *configuration = [ARWorldTrackingSessionConfiguration new]; This has been deprecated in favor of the previous line in XCode 9 beta 5.
// Specify that we do want to track horizontal planes. Setting this will cause the ARSCNViewDelegate
// methods to be called when scenes are detected
//configuration.planeDetection = ARPlaneDetectionHorizontal;
// Run the view's session
[self.sceneView.session runWithConfiguration:configuration options:ARSessionRunOptionResetTracking];
}
-(void)destroyScene {
bottomPlane = nil;
[self.sceneView setScene:nil];
[self.sceneView setDebugOptions:nil];
self.boxes = nil;
self.planes = nil;
self.sceneView.delegate = nil;
}
-(void)destroySession {
[self.sceneView.session pause];
[self.sceneView setSession:nil];
}
These destroy methods are used when the view disappears. I am also restarting the AR Session on a button press, but it is not through these methods. It is as follows:
-(void)resetPressed{
NSLog(#"Reset Pressed");
[_sceneView.session pause];
SCNScene *scene = [[SCNScene alloc] init];
[_sceneView setScene:scene];
[_sceneView.scene.rootNode enumerateChildNodesUsingBlock:^(SCNNode * _Nonnull child, BOOL * _Nonnull stop) {
[child removeFromParentNode];
}];
ARWorldTrackingConfiguration *configuration = [[ARWorldTrackingSessionConfiguration ARWorldTrackingConfiguration] init];
[_sceneView.session runWithConfiguration:configuration options:ARSessionRunOptionResetTracking | ARSessionRunOptionRemoveExistingAnchors];
}
Hope it helps.
Here's an answer working with Swift 4.2 and iOS 12.
To present UI defined in another view controller over your AR scene, create your view controller instance and set it's modalPresentationStyle property to .overCurrentContext:
EXAMPLE:
func showMaterialPicker(completion: (Texture?) -> Void) {
// create an instance of your view controller, I have convenience functions
// setup to do this via an extension on UIViewController
guard let materialPicker = MaterialCategoriesViewController.instance(from: .product) else {
print("Unable to instantiate MaterialCategoriesViewController, bailing")
return
}
// set presentation style and transition style
materialPicker.modalPresentationStyle = .overCurrentContext
materialPicker.modalTransitionStyle = .crossDissolve
// present the controller
present(materialPicker, animated: true, completion: nil)
}
Bonus tip:
To make your overlay appear to slide up from the bottom like a drawer, set
materialPicker.modalTransitionStyle = .coverVertical
then constrain your views in your overlay view controller a comfortable height from the bottom and set the background color of the view controllers view to UIColor.clear.
If you want to darken the AR view while your overlay is displayed you can set the background color to a black color with an opacity/alpha value of approximately 0.75.
Something like this:
self.view.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.75)
or in the storyboard:
In the screenshot above I have a tableview pinned to the bottom and sides of the overlay view controllers view, and a height constraint of 300.
When done this way you can still see the AR view behind the overlay view controller and the scene continues to render.
Inside viewDidLoad: create a tap event.
arKitView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:))))
Inside viewWillAppear:
arView.session.run(configuration)
And then define the tap event. In which first tap will pause the session and second tap will resume the session and so on.

How to make a AVPlayerViewController go to fullscreen programmatically?

I'm trying to make a AVPlayerViewController go to full screen mode programmatically, coming from "embedded" mode, however this does not appear to be possible with the published API.
Is there a workaround that I'm missing? I'm interested in obtaining the same animation to the one that you get when the user presses the full screen button on the bottom right of the controls.
Using MPMoviePlayerController is not a viable alternative since I might have more than one video playing at a time.
Thanks.
AVPlayerViewController is a subclass of UIViewController, so it is presentable like any other view controller subclass. Are you able to use presentViewController:animated:completion?
self.avPlayerController.modalPresentationStyle = UIModalPresentationOverFullScreen;
[self presentViewController:self.avPlayerController animated:YES completion:nil];
This then shows the "Done" button in the top left-hand corner.
Updated for iOS 11
There is no supported way to programmatically go fullscreen with AVPlayerViewController (a bit of an oversight in my opinion).
However, AVPlayerViewController does contain a private method that does exactly that. You'll have to decide for yourself whether you'd want to use it or not given you're not supposed to call private methods.
AVPlayerViewController+Fullscreen.h
#import <AVKit/AVKit.h>
#interface AVPlayerViewController (Fullscreen)
-(void)goFullscreen;
#end
AVPlayerViewController+Fullscreen.m
#import "AVPlayerViewController+Fullscreen.h"
#implementation AVPlayerViewController (Fullscreen)
-(void)goFullscreen {
NSString *selectorForFullscreen = #"transitionToFullScreenViewControllerAnimated:completionHandler:";
if (#available(iOS 11.3, *)) {
selectorForFullscreen = #"transitionToFullScreenAnimated:interactive:completionHandler:";
} else if (#available(iOS 11.0, *)) {
selectorForFullscreen = #"transitionToFullScreenAnimated:completionHandler:";
}
SEL fsSelector = NSSelectorFromString([#"_" stringByAppendingString:selectorForFullscreen]);
if ([self respondsToSelector:fsSelector]) {
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:fsSelector]];
[inv setSelector:fsSelector];
[inv setTarget:self];
NSInteger index = 2; //arguments 0 and 1 are self and _cmd respectively, automatically set
BOOL animated = YES;
[inv setArgument:&(animated) atIndex:index];
index++;
if (#available(iOS 11.3, *)) {
BOOL interactive = YES;
[inv setArgument:&(interactive) atIndex:index]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
index++;
}
id completionBlock = nil;
[inv setArgument:&(completionBlock) atIndex:index];
[inv invoke];
}
}
#end
UPDATE: Swift 4 version of ToddH's answer:
private func enterFullscreen(playerViewController: AVPlayerViewController) {
let selectorName: String = {
if #available(iOS 11.3, *) {
return "_transitionToFullScreenAnimated:interactive:completionHandler:"
} else if #available(iOS 11, *) {
return "_transitionToFullScreenAnimated:completionHandler:"
} else {
return "_transitionToFullScreenViewControllerAnimated:completionHandler:"
}
}()
let selectorToForceFullScreenMode = NSSelectorFromString(selectorName)
if playerViewController.responds(to: selectorToForceFullScreenMode) {
playerViewController.perform(selectorToForceFullScreenMode, with: true, with: nil)
}
}
In iOS11 there are 2 new properties for AVPlayerViewController: entersFullScreenWhenPlaybackBegins and exitsFullScreenWhenPlaybackEnds. You can enable full screen mode right after playback begins and disable it when playback ends with these properties. If you need to enable fullscreen mode after some delay you can use private API methods as ToddH mentioned in his answer. However in iOS11 _transitionToFullScreenViewControllerAnimated:completionHandler: method is not available anymore, there is the same method called _transitionToFullScreenAnimated:completionHandler:. The second method accepts the same arguments as the first one.
I can show an example how to use it. First of all you need to create AVPlayerViewController instance in your UIViewController:
private let playerController : AVPlayerViewController = {
if let urlForPlayer = URL(string: "your_video_url") {
$0.player = AVPlayer(url: urlForPlayer)
}
return $0
} (AVPlayerViewController())
Then you need to setup view for AVPlayerViewController and add it to your current controller view. Function setupAVplayerController can do it for you:
private func setupAVplayerController() {
self.addChildViewController(self.playerController)
self.playerController.view.frame = CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200.0)
self.view.addSubview(self.playerController.view)
self.playerController.didMove(toParentViewController: self)
}
Function enterFullscreen forces full screen mode for AVPlayerViewController:
private func enterFullscreen(playerViewController:AVPlayerViewController) {
let selectorName : String = {
if #available(iOS 11, *) {
return "_transitionToFullScreenAnimated:completionHandler:"
} else {
return "_transitionToFullScreenViewControllerAnimated:completionHandler:"
}
}()
let selectorToForceFullScreenMode = NSSelectorFromString(selectorName)
if playerViewController.responds(to: selectorToForceFullScreenMode) {
playerViewController.perform(selectorToForceFullScreenMode, with: true, with: nil)
}
}
And now you need to call all these functions where you need it, for example in viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//Your code
self.setupAVplayerController()
self.playerController.player?.play()
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.enterFullscreen(playerViewController:self.playerController)
}
}
Don't forget that this solution based on private API calls that is not recommended to use.
As a little iOS 14 update to ToddH's answer: the private API to call is enterFullScreenAnimated:completionHandler:. So here's an extension on AVPlayerViewController to enter full screen.
extension AVPlayerViewController {
func enterFullScreen(animated: Bool) {
perform(NSSelectorFromString("enterFullScreenAnimated:completionHandler:"), with: animated, with: nil)
}
func exitFullScreen(animated: Bool) {
perform(NSSelectorFromString("exitFullScreenAnimated:completionHandler:"), with: animated, with: nil)
}
}
This implementation doesn't offer a completion callback. If you pass a Swift closure to the completionHandler parameter, it crashes in the underlying Obj-C API. I haven't investigated how to pass the closure to make it work.
Swift 3 version for the answer of ToddH:
extension AVPlayerViewController {
func goFullScreen() {
let selector = NSSelectorFromString("_transitionToFullScreenViewControllerAnimated:completionHandler:")
if self.responds(to: selector) {
// first argument is animated (true for me), second is completion handler (nil in my case)
self.perform(selector, with: true, with: nil)
}
}
}
You can just set the videoGravity property of AVPlayerViewController.
if(fullscreen)
{
[self.avPlayerController
setVideoGravity:AVLayerVideoGravityResizeAspectFill];
}
else
{
[self.avPlayerController
setVideoGravity:AVLayerVideoGravityResizeAspect];
}
For an 'embedded' AVPlayerViewController instance, it is quite easy to programmatically have it start playback in full screen mode, and without hacking anything (calling private methods). You just need to set its entersFullScreenWhenPlaybackBegins property to true.
You need to add the controller as a child VC to the main VC, and that's basically it. In viewDidAppear(_:) you need to call play() method on the controller's player property - playback will be automatically started in fullscreen.
It's often best to check Apple sample code for these kind of tricky APIs; I think this one might be useful for a lot of AVPlayer use cases: Using AVKit in iOS.
I did not have the need to use any restricted code.
For this, I am assuming that you have added the AVPlayerViewController as a child view controller.
Then for that you will first have to remove the child view controller and then present it again as a fullscreen controller as well attach the AVPlayer view properly to it's parent view.
Here is how I did it. Please note that I am using a library called Easy Peasy for restoring the playerVC.view constraints - one can do that with proper constraints as well.
#objc func fullscreenButtonClicked() {
playerVC.willMove(toParentViewController: nil)
playerVC.view.removeFromSuperview()
playerVC.removeFromParentViewController()
self.present(self.playerVC, animated: false, completion: {
self.playerVC.view.easy.layout(Top(), Right(), Left(), Bottom())
})
}
Its pretty simple, just set
playerViewController.videoGravity = .resizeAspectFill
and it goes full screen:)

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)

UIActivityViewController freezes when email or text but not fb or Twitter

I have a very standard implementation of UIActivityViewController. When I use Twitter or Facebook, the view controller is dismissed, and the app continues working. However, when I email or text the same content, the view controller is dismissed but the app freezes (not crashes). Everything is still on screen but frozen - no input etc.
Perhaps the Mail or Message apps have not released control back to my app? Is there a way using Instruments to analyze what's going on?
Thanks!
I am getting a leak from this part from NSArray as the offenders
- (void)postToFacebook:(UITapGestureRecognizer *)sender
{
NSString *postText = #"Testing";
UIImage *imageToPost = [self captureTheScreenImage];
NSArray *postItems = #[postText, imageToPost];
UIActivityViewController *activityPostVC = [[UIActivityViewController alloc]initWithActivityItems:postItems applicationActivities:nil];
NSArray *excludedItems = #[UIActivityTypePostToWeibo,UIActivityTypePrint,UIActivityTypeCopyToPasteboard,UIActivityTypeAssignToContact,UIActivityTypeSaveToCameraRoll, UIActivityTypeMail, UIActivityTypeMessage];
[activityPostVC setExcludedActivityTypes:excludedItems];
[self presentViewController:activityPostVC animated:YES completion:nil];
}
This issue happened to me too when using multiple UIWindow objects at the same time.
Upon dismissal of the UIActivityViewController, the presenting windows contents are not restored correctly. Specifically, the window's first subview (UILayoutContainerView) is missing its constraints to the superview. This causes the window the width of the UILayoutContainerView to be zero causing the window appear transparent and reveal the window underneath it but not allowing user interaction.
The fix can be to place an empty transparent window on top of the current window and present the UIActivityViewController from an empty view controller associated with that new window. When the UIActivityViewController is dismissed, we can dispose of the empty window that it was presented from.
import Foundation
private var previousWindow: UIWindow?
private var activityViewControllerWindow: UIWindow?
extension UIViewController {
fileprivate var isActivityViewControllerWindowPresented: Bool {
return activityViewControllerWindow?.isKeyWindow ?? false
}
func presentActivityViewController(_ activityViewController: UIActivityViewController, animated: Bool = true, completion: (() -> Void)? = nil) {
if isActivityViewControllerWindowPresented {
return
}
let window = UIWindow(frame: view.window!.frame)
previousWindow = UIApplication.shared.keyWindow
activityViewControllerWindow = window
window.rootViewController = UIViewController()
window.makeKeyAndVisible()
let activityCompletionClosure = activityViewController.completionWithItemsHandler
activityViewController.completionWithItemsHandler = { [weak self] (activityType, completed, returnedItems, activityError) in
self?.cleanUpActivityViewControllerWindow()
activityCompletionClosure?(activityType, completed, returnedItems, activityError)
}
window.rootViewController?.present(activityViewController, animated: animated, completion: completion)
}
fileprivate func cleanUpActivityViewControllerWindow() {
previousWindow?.makeKeyAndVisible()
activityViewControllerWindow?.rootViewController = nil
activityViewControllerWindow = nil
}
}
Yes, there is a way, like you mentioned, using Instruments. But If I were to foreshadow your results, I'd say you might want to do network calls on a non-UI thread, somewhere in the background so that your UI thread can do its thang while your app talks to Twitter or Facebook.

UITableView push segue triggered multiple times

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.

Resources