I have an issue with UIDocumentInteractionController which is used to open a PDF document.
The problem at this point is when user interacts with the menu my clicking on the Open in {app_name} action, the menu itself fails to close after user interaction leaving the menu open when user comes back to the app.
Dismissing the menu at this point requires user to tap twice on the X in order for menu to be dismissed.
I also tried to close the menu dynamically when app returns to active state by running dismissMenuAnimated on UIDocumentInteractionController instance but it does not work in this case, it does however dismiss the dialog if no interaction was made on it and the method is called.
One note, this issue is only present on never iOS version 13+. On older OS the menu is closed after interaction as expected.
We tried using this code other than all react-native stuff we tried:
#property (nonatomic, strong) UIDocumentInteractionController *documentInteractionController;
#property (atomic) CGRect rect;
RCT_EXPORT_METHOD(openDocumentInteractionControllerWithFileURL:(NSURL *)fileURL){
if (!fileURL) {
// Return NO because there was no valid fileURL.
return;
}
UIViewController *rootCtrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
// Open "Open in"-menu
self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
self.documentInteractionController.delegate = self;
BOOL sucess; // Sucess is true if it was possible to open the controller and there are apps available
sucess = [self.documentInteractionController presentOpenInMenuFromRect:self.rect inView:rootCtrl.view animated:YES];
if(!sucess){
// Inform app that the activity has finished
// Return NO because the service was canceled and did not finish because of an error.
// http://developer.apple.com/library/ios/#documentation/uikit/reference/UIActivity_Class/Reference/Reference.html
}
}
RCT_EXPORT_METHOD(dismissDocumentInteractionController){
// Hide menu
[self.documentInteractionController dismissMenuAnimated:YES];
}
But without success.
You need to close the interaction controller menu after user performs his selection in this menu.
You can do it in UIDocumentInteractionControllerDelegate like this:
- (void)documentInteractionController:(UIDocumentInteractionController *)controller willBeginSendingToApplication:(NSString *)application {
[self.documentInteractionController dismissMenuAnimated:YES];
}
Related
I am trying to print a txt file from within a iPad 8.x application. So, I have this code:
- (void)onOpenWith:(UIButton *)theButton path:(NSString *)path
{
NSURL *URL = [NSURL fileURLWithPath:path];
if (URL) {
self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:URL];
self.documentInteractionController.delegate = self;
[self.documentInteractionController presentPreviewAnimated:YES];
}
}
#pragma mark - UIDocumentInteractionControllerDelegate
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller
{
return self;
}
- (UIView *)documentInteractionControllerViewForPreview:(UIDocumentInteractionController *)controller
{
return self.view;
}
- (CGRect)documentInteractionControllerRectForPreview:(UIDocumentInteractionController *)controller
{
return self.view.frame;
}
Now everything is going as expected, I see a preview of the file, then I touch the icon on the top right corner of the screen and I am able to share the document in those applications that can handle it. However, if I touch PRINT I get this error message:
Application tried to present inside popover with transition style
other than UIModalTransitionStyleCoverVertical
and the app crashes. Sure, i understand it, but to which viewcontroller should I apply this transition? I have no control on the popover showing the print dialog...
In iOS 7 (real iPad not simulator) everything works...
Can anybody help me?
Thanks
Fabio
I also came across this issue, and the solution was that I had to build and run my app with Xcode 6 installed.
On my older machine, I have Xcode 5.1.1, and when run from there, this same issue appeared, and some more issues from this view (like cannot dismiss mail controller when opened from the top right corner).
I have an app with tabbed view: search | subscribes | messages
All tab buttons are visible at app start even for unauthorized user.
But when unauthorized user clicks on let's say messages tab, I want to show "MessagesViewController" but it must show grey screen:
Please sign-in. [button sign-in] [button registration]
When user clicks [button sign-in] - modal UIViewController appears. After sign-in user goes back to "MessagesViewController" tab, but this time user can see his messages. Same grey screen must be in subscribes tab.
I'm new to iOS so I want to know what is a proper way to do this. Subscribes and messages are table views. Do I need to place another view on top of tables to overlap them and then hide it after authorization? Or can I create one separate "unauthorized" controller, connect it with tabs and reuse it? If so, how can I connect messages and subscribes controllers back to tabs after sign in?
Create a baseViewController, make all three viewController's extend that base.
Think of a method that will show the user "please sign-in" message. A gray UIView (with a UILabel and two UIButtons) overriding all the content seems good to me.
Add the buttons on that gray UIView, with self s as targets
Check in the viewWillAppear method whether the user is not logged in or not. Show the gray view if not.
Create a boolean and before you fire the segue you proof, if the boolean is yes or no. When the boolean is yes, the segue is performed. Otherwise a segue to your Login View is performed.
I hope this solves your problem
you can use
- (void) viewWillAppear:(BOOL)animated
to do this
#interface Messages
{
BOOL loggedIn;
UILabel *pleaseSignIn;
UIButton *signin;
UIButton *register;
}
- (void) viewWillAppear:(BOOL)animated
{
if([[[NSUserDefaults standardUserDefaults] valueForKey:#"logged"] isEqualToString:#"YES"])
{
loggedIn = YES;
}
else
{
loggedIn = NO;
}
if(loggedIn)
{
//Display messages
pleaseSignin.hidden = YES;
signin.hidden = YES;
register.hidden = YES;
}
else
{
//Remove messages view
pleaseSignin.hidden = NO;
signin.hidden = NO;
register.hidden = NO;
}
}
Are you able to edit images with QLPreviewController?
For example, preview an image and crop it? If not, what is the editing property on QLPreviewController for? It doesn't seem to change anything. (Previewing files is working fine)
Here is an example:
QLPreviewController *previewController=[[QLPreviewController alloc]init];
previewController.delegate=self;
previewController.dataSource=self;
[previewController setCurrentPreviewItemIndex:selectedIndex];
[previewController setEditing:YES animated:YES];
[self presentModalViewController:previewController animated:YES];
Delegate methods:
- (id <QLPreviewItem>)previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index
{
file = [folder.files objectAtIndex:index];
controller.title = file.name;
CustomQLPreviewItem *customQLPreviewItem = [[CustomQLPreviewItem alloc] init];
customQLPreviewItem.previewItemURL = [NSURL fileURLWithPath:file.uri];
customQLPreviewItem.previewItemTitle = file.name;
return customQLPreviewItem;
}
- (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller
{
return folder.file.count;
}
QLPreviewController does not support editing in the way you are thinking.
the editing property is actually inherited from UIViewController and from here:
UIViewController Class Reference
editing A Boolean value indicating whether the view controller
currently allows the user to edit the view contents.
#property(nonatomic, getter=isEditing) BOOL editing Discussion If YES,
the view controller currently allows editing; otherwise, NO.
If the view is editable and the associated navigation controller
contains an edit-done button, then a Done button is displayed;
otherwise, an Edit button is displayed. Clicking either button toggles
the state of this property. Add an edit-done button by setting the
custom left or right view of the navigation item to the value returned
by the editButtonItem method. Set the editing property to the initial
state of your view. Use the setEditing:animated: method as an action
method to animate the transition of this state if the view is already
displayed.
Availability Available in iOS 2.0 and later. See Also –
setEditing:animated: – editButtonItem Related Sample Code BonjourWeb
iPhoneCoreDataRecipes Declared In UIViewController.h
EDIT: Check below for solution.
I am working on a login screen for my app and I have it working for the most part except for a few edge cases. Ive set things up so that I have a segue from my UITabBar in the story board that I trigger in the app delegate applicationDidBecomeActive: method. As I said it works fine on all but one edge case I've found so far.
My app uses some modal view controllers, some of which are UIActivityViewControllers if that makes a difference, to enter and edit some core data entities. If one of these modal view controllers is opened when the app goes to the background, it will always show up when the app is reopened and my login doesn't show. I get the following console msg
Warning: Attempt to present <UINavigationController: 0x1d51e320> on <MPTabBarViewController: 0x1d5b4810> which is already presenting <UIActivityViewController: 0x1e38fc40>
Here is my code
- (void) displayLogin{
NSLog(#"%s", __PRETTY_FUNCTION__);
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
NSDate *lastDate = [[NSUserDefaults standardUserDefaults] objectForKey:MPLastCloseDate];
NSTimeInterval timeDiff = [[NSDate date] timeIntervalSinceDate:lastDate];
int seconds = timeDiff;
if ([[NSUserDefaults standardUserDefaults] integerForKey:MPPassCodeDelay] == MPScreenLockAlways || seconds >= 300) {
NSLog(#"Should see login");
[tabBarController performSegueWithIdentifier:#"loginScreen" sender:self];
}
}
I understand exactly what this msg is telling me, the tab bar is already presenting a modal controller so it can't present another one. So my question is this, Is there a better way to implement this so that the login will always show, even over top of the modal views?
Okay here is my current solution
as suggested by Bartu and requested to be shared by Shawn
I have a working singleton loginManager class that requires 1 call in app delegate and 1 call in any view controller that could be called to present as modal. I was unable to figure out how to do this as suggested with a ViewController category, but hey a few includes and method calls aren't so bad. I included it in App-Prefix.pch, so its available everywhere. It is written for ARC, so if you like managing your own memory you'll need to modify the singleton for that. The last caveat, at current you will need to roll your own viewController for the login screen. Just look for the commented section in the implementation with all the stars, and put your own view controller there. Mine is still in my app storyboard, its basically 4 digit pin that checks for a match in the keychain and dismisses itself for the correct pin. I may pull that out of my storyboard and nib it so it could be packaged with the loginManager and let it become my first gitHub project at some future date though.
You can configure it to display login for every time the app opens or after a delay with properties. The delay time is also a property set in seconds. It will also block out your apps UI for the few seconds it takes to get the login displayed with a splash using your apps Default.png. This is also configurable with a property.
I would love to get some feedback on this, and if anyone can tell me how to do a category so the extra call in viewControllers is not needed that would be great! Enjoy!
AppDelegate:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
[self.window makeKeyAndVisible];
// these calls are all optional
[[VHLoginManager loginManager] setShouldBlockUIWithSplashOnResume:NO];
[[VHLoginManager loginManager] setSecondsRequiredToPassBeforeLockDown:1000];
[[VHLoginManager loginManager] setScreenLockRequirment:VHLMScreenLockDelayed];
// this is the only required call to run with defaults - always login and block UI with splash while login loads
[[VHLoginManager loginManager] presentLogin];
}
Any viewController that may presented as modal at some point
- (void)viewDidLoad
{
[super viewDidLoad];
[[VHLoginManager loginManager] registerViewControllerIfModal:self];
}
The loginManager class
header:
// VHLoginManager.h
// Created by Victor Hudson on 5/31/13.
// Copyright (c) 2013 Victor Hudson. All rights reserved.
// Use if you like but be nice and leave my name
#import <Foundation/Foundation.h>
#define VHLMLastCloseDate #"VHLMLastCloseDate"
#define VHLMPassCodeDelay #"VHLMPassCodeDelay"
typedef enum {
VHLMScreenLockAlways = 0,
VHLMScreenLockDelayed = 1,
} VHLMScreenLockRequirement;
#interface VHLoginManager : NSObject
#property (nonatomic) BOOL shouldBlockUIWithSplashOnResume;
// defaults to yes so app contents arent visible before the login screen appears
#property (nonatomic) int secondsRequiredToPassBeforeLockDown;
// defaults to 5 minutes (300)
#pragma mark - Class Methods
+ (VHLoginManager *)loginManager;
// returns the singleton login manager
#pragma mark - Manager Methods
- (void) presentLogin;
// will determine if login should be presented an do so if needed
- (void) registerViewControllerIfModal:(UIViewController *)controller;
// any view controllers that are presented modally should call this with self as controller in viewDidLoad - the pupose of this manager is so login shows even over top of modals
- (void) setScreenLockRequirment:(VHLMScreenLockRequirement) requirement;
// deafaults to always if not adjusted
#end
implementation:
// VHLoginManager.m
// Created by Victor Hudson on 5/31/13.
// Copyright (c) 2013 Victor Hudson. All rights reserved.
// Use if you like but be nice and leave my name
#import "VHLoginManager.h"
static VHLoginManager *loginManager = nil;
#interface VHLoginManager ()
#property (nonatomic, strong) UIViewController *currentModalViewController;
#property (nonatomic) VHLMScreenLockRequirement screenLockrequirement;
#end
#implementation VHLoginManager
#pragma mark - Manager Methods
- (void) presentLogin
{
// NSLog(#"%s", __PRETTY_FUNCTION__);
if ([[NSUserDefaults standardUserDefaults] integerForKey:VHLMPassCodeDelay] == VHLMScreenLockAlways || [self timeSinceLastClose] >= self.secondsRequiredToPassBeforeLockDown) {
//NSLog(#"User should see login");
// determine who the presenting view controller should be
UIViewController *viewController;
if (self.currentModalViewController && self.currentModalViewController.presentingViewController != nil) {
// NSLog(#"We have a modal view controller on top");
viewController = self.currentModalViewController;
} else {
// NSLog(#"We have NO modal view controller on top");
// get the root view controller of the app
viewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
}
//********************************************************************************************************************************************************************************
// *** This is still tied into my app storyboard and should be made into a viewcontroller with nib to be portable with loginManager for now implement and present your own loginViewController
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
UINavigationController *navController = [storyboard instantiateViewControllerWithIdentifier:#"appLoginScreen"];
//********************************************************************************************************************************************************************************
// present the login to user
[viewController presentViewController:navController animated:NO completion:nil];
}
}
- (void) setScreenLockRequirment:(VHLMScreenLockRequirement) requirement
{
_screenLockrequirement = requirement;
[[NSUserDefaults standardUserDefaults] setInteger:self.screenLockrequirement forKey:VHLMPassCodeDelay];
}
- (void) registerViewControllerIfModal:(UIViewController *)controller
{
// NSLog(#"%s", __PRETTY_FUNCTION__);
if (controller.presentingViewController) {
NSLog(#"Registering a modalViewController");
self.currentModalViewController = controller;
}
}
#pragma mark - Private Methods
- (void) timeStampForBackground
{
// NSLog(#"%s", __PRETTY_FUNCTION__);
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:VHLMLastCloseDate];
[self setDisplaySplashForBackgroundResume];
}
- (int) timeSinceLastClose
{
return [[NSDate date] timeIntervalSinceDate:[[NSUserDefaults standardUserDefaults] objectForKey:VHLMLastCloseDate]];
}
#pragma mark Splash Screen management
- (void) setDisplaySplashForBackgroundResume
{
// NSLog(#"%s", __PRETTY_FUNCTION__);
if (self.shouldBlockUIWithSplashOnResume) {
// dismiss all keyboards and input views
UIView *topView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];
[topView endEditing:YES];
// Don't show a splash screen if the application is in UIApplicationStateInactive (lock/power button press)
UIApplication *application = [UIApplication sharedApplication];
if (application.applicationState == UIApplicationStateBackground) {
UIImageView *splash = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default"]];
splash.frame = application.keyWindow.bounds;
[application.keyWindow addSubview:splash];
}
}
}
- (void) removeSplashScreen
{
// NSLog(#"%s", __PRETTY_FUNCTION__);
if (self.shouldBlockUIWithSplashOnResume) { // we should have a splash image up if true
// so remove it
UIWindow *thewindow = [[UIApplication sharedApplication] keyWindow];
if ([[thewindow subviews] count] > 1) {
[NSThread sleepForTimeInterval:1.0];
[[[thewindow subviews] lastObject] removeFromSuperview];
}
}
}
#pragma mark - Class Management
//prevent additional instances
+ (id)allocWithZone:(NSZone *)zone
{
return [self loginManager];
}
+ (VHLoginManager *)loginManager
{
if (!loginManager) {
//Create The singleton
loginManager = [[super allocWithZone:NULL] init];
}
return loginManager;
}
- (id) init
{
// If we already have an instance of loginManager
if (loginManager) {
//Return The Old One
return loginManager;
}
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(timeStampForBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(removeSplashScreen)
name:UIApplicationDidBecomeActiveNotification
object:nil];
self.shouldBlockUIWithSplashOnResume = YES;
self.secondsRequiredToPassBeforeLockDown = 300;
if (![[NSUserDefaults standardUserDefaults] integerForKey:VHLMPassCodeDelay]) {
[self setScreenLockRequirment:VHLMScreenLockAlways];
}
}
return self;
}
#end
I had the same problem a short time ago, my solution for this problem is to have a reference to any modal view which is currently presented in your app delegate. So, you can know if your tab bar controller is already presenting a modal controller and if it's the case, you can present your login view over your current modal view.
What I did was to have a switch in my appDelegate. when the app started, if the user had not logged in, I created the login view and make it the window's rootViewController. When the user successfully logged in, I used an animation block to set that view's alpha to 0, then created a UITabBarController, populated it, made it the window's rootViewController (with an alpha of 0, then animated it's alpha to 1). Worked really well. Not sure how to do this with storyboards though.
EDIT: now getting familiar with storyboards. So what you would do is not use the Main.storyboard per se (remove it from info.plist), then add a LoginViewController as a view, and have your UITabbarController there too - but nothing is the initial view controller. You obviously have to name each view so you can create it in code, but asking the Storyboard to create such and such a view controller
So in App Delegate, if logged in, instantiate the tab bar controller and add it as the root view controller. If the user has not logged in, create the LoginView and add it as rootview controller. If the user does login, have some method on the LoginViewController so it can ask the delegate to switch to the tab bar controller.
I'm using a UIWebView, and it pops an alert when a button is clicked(from javascript). There is another button (in native side), which closes controller, so deallocs also UIWebView.
The problem is, if I touch the button in UIWebView, and touch to close button before alert is populated, my controller and UIWebView are deallocated, but alert remains on screen. Then if I click any button on alert, application crashes and gives following error:
[UIWebView modalView:didDismissWithButtonIndex:]: message sent to deallocated instance
And this method is called from private method
[UIModalView(Private) _popoutAnimationDidStop:finished:]
I'm using ARC, and my dealloc is like this:
- (void)dealloc {
[_myWebView stopLoading];
_myWebView.delegate = nil;
_myWebView = nil;
}
But this does not solve my problem because I think UIModalView has a reference of my webview as a delegate, and I could not set it to nil because its private.
How can I solve it?
Regards
Find a way to set the delegate on UIAlertView to nil before deallocating UIWebView.
This is an Apple bug in their handling of the alert view. Open a bug report.
In the meantime, here are some workarounds:
Create a category on UIAlertView:
#interface UIAlertView (QuickDismiss) #end
#implementation UIAlertView (QuickDismiss)
- (void)__quickDismiss
{
[self dismissWithClickedButtonIndex:self.cancelButtonIndex animated:NO];
}
#end
Now, in your view controller's dealloc method, call this:
[[UIApplication sharedApplication] sendAction:#selector(__quickDismiss) to:nil from:nil forEvent:nil];
This will dismiss all alert views that are currently open, including the ones displayed by your web view.
If that does not work, you can always iterate all subviews of all UIApplication.sharedApplication.windows objects, checking whether [view.class.description hasPrefix:#"UIAlertView"] is true, and dismissing that. This is a less elegant method than the previous one, and should be last resort.
Good luck.
Finally, I find a great solution which actully works. I use method swizzle to hook UIAlertView Delegate function - (void)didPresentAlertView:(UIAlertView *)alertView
- (void)didPresentAlertView:(UIAlertView *)alertView
{
if ([self.delegate respondsToSelector:#selector(didPresentAlertView:)]) {
[self.delegate didPresentAlertView:alertView];
}
if ([self.delegate isKindOfClass: [UIWebView class]]) {
uiWebView = self.delegate;
}
}
I just retain UIWebView instance in this function so that the UIWebView instance as UIAlertView's delegate will not be released before UIAlertView instance being released.