I have this strange problem that only occurs in iPhone 4 with iOS 7. When I try to present a UIDocumentInteractionController on to the screen, application is stuck on presentPreviewAnimated method. The same thing happens in a different place when I try to use MPMoviePlayerViewController, only this time it is stuck on initWithContentURL. No error is thrown. I know that it is not much info I provided, but I don't have a clue how these things can be related and thus I don't know what information would be helpful. In my project I am using the following structure of Views.
HomeViewController * homeViewController = [[HomeViewController alloc] initWithNibName:#"HomeViewController" bundle:nil];
MenuTableViewController * menuViewController = [[MenuTableViewController alloc] initWithStyle:UITableViewStylePlain];
ECSlidingViewController * slidingViewController = [[ECSlidingViewController alloc] init];
slidingViewController.topViewController = homeViewController;
slidingViewController.underLeftViewController = menuViewController;
UINavigationController * navigationController = [[UINavigationController alloc] initWithRootViewController:slidingViewController];
self.window.rootViewController = navigationController;
For example when I debug the code:
_docController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
_docController.delegate = self;
[_docController presentPreviewAnimated:YES];
NSLog(#"Document showed");
The #"Document showed" message is never logged.
Application works great on the iPad and iPhone 5.
In my project I am using CocoaPods.
Please ask any questions that can help with finding the solution.
Try below code, hope it will be helpful.
set UIDocumentInteractionControllerDelegate in .h file
#interface NextViewController : UIViewController <UIDocumentInteractionControllerDelegate>
Create object of UIDocumentInteractionController and initialise it.
- (IBAction)openDocument:(id)sender
{
NSString* path = [[NSBundle mainBundle] pathForResource:#"fileName" ofType:#"fileType"]; // File types - .pdf, .txt, .jpg, .png, or any other.
if (path)
{
NSURL* url = [NSURL fileURLWithPath:path];
UIDocumentInteractionController* docController = [UIDocumentInteractionController interactionControllerWithURL:url];
docController.delegate = self;
[docController presentPreviewAnimated:YES];
}
}
Implement its delegate method.
#pragma mark - UIDocumentInteractionControllerDelegate
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller
{
return self;
}
Turned out that iPhone 4 manages threads differently than other newer devices. I had an error in not directly related place resulting in an endless loop.
while (self.delegate && ![self.delegate dataLoaderOperationCanStop:self])
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
All other devices showed those ViewControllers with no problem, only iPhone 4 was stuck. I hope that my problem will help someone with similar strange application behavior.
Related
I have a SFSafariViewController opening at the click of a button inside a UIActionSheet. It has been working fine and is still working fine on all the versions of iOS except iOS 11. Is there something they have changed regarding the SFSafariViewController in iOS 11 or in Xcode 9.0 that might have caused this issue?
UPDATE - So it seems like its Xcode 9.0 that is causing this issue. I have tried running it on different iOS versions and all of them seem to be giving this issue. It used to work fine when I ran it using Xcode 8.3.3, something I don't have anymore :(
Here's the code -
- (void)presentWebView:(NSString *)url {
url = [url stringByReplacingOccurrencesOfString:#" " withString:#"+"];
url = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *URL = [NSURL URLWithString:url];
if (URL) {
if ([SFSafariViewController class] != nil) {
SFSafariViewController *sfvc = [[SFSafariViewController alloc] initWithURL:URL];
sfvc.delegate = self;
[self.tabBarController presentViewController:sfvc animated:YES completion:nil];
} else {
if (![[UIApplication sharedApplication] openURL:URL]) {
NSLog(#"%#%#",#"Failed to open url:",[url description]);
}
}
} else {
// will have a nice alert displaying soon.
}
}
I've managed to fix this in my code. I hope this helps anyone else with a similar problem.
I had the exact same problem as described here. I tried everything above and unfortunately nothing worked.
In my app there were different windows. The fix was to ensure the window that would show SFSafariViewController was 'key' before presenting it. For example:
class MyViewController: UIViewcontroller {
func showSafariViewController() {
// imagine we have 2 windows, one for 'normal' content (key window) and one for 'other' content. lets say we're showing the 'other' content window, and have hidden the 'normal' window. you can see the 'other' content window in the app, but it won't be the key window!
let window = // get the 'other' content window
// make sure we make it the key window before presenting safari
window.makeKey()
// now present safari and celebrate victory by triumphantly slurping on your hot black coffee
let mySafariViewController = SFSafariViewController(...)
self.present(mySafariViewController ...)
}
}
I suspect Apple are searching for a SFSafariViewController instance in the window UIApplication.shared.keyWindow. Perhaps they're adding a child view from somewhere else. In the documentation it states The user's activity and interaction with SFSafariViewController are not visible to your app, so perhaps it's the bug is something related to an added level of security https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller
I have tried to do it using delay and make view of controller loading.Both are working for me.
Method 1. Using delay.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let controller = SFSafariViewController(url: url)
controller.modalPresentationStyle = .overFullScreen
self.present(controller, animated: true, completion: nil)
controller.delegate = self
}
Method 2. Loading view.
let controller = SFSafariViewController(url: url)
let _ = controller.view
controller.modalPresentationStyle = .overFullScreen
self.present(controller, animated: true, completion: nil)
controller.delegate = self
Very similar to https://openradar.appspot.com/29108332
To fix it, you can disable the lazy loading of the view:
SFSafariViewController *viewController = [[SFSafariViewController alloc] init...];
(void)viewController.view;
...
[controller presentViewController:viewController animated:YES completion:nil];
Update
Turns out the old answer didn't work, it just worked since I put breakpoints. If I added a thread sleep seems it worked in XCode9, but that's not the best solution. Anyone have another better solution?
SFSafariViewController *sfcontroller = [[SFSafariViewController alloc] initWithURL:url];
if (#available(iOS 11.0, *)) {
[NSThread sleepForTimeInterval:0.5f];
}
sfcontroller.delegate = self;
[controller presentViewController:sfcontroller animated:NO completion:nil];
Old Answer
I have the same issue as genaks and tinkered around with SFViewController.
Seems like this code works for me
SFSafariViewController *sfcontroller = [[SFSafariViewController alloc] initWithURL:url];
if (#available(iOS 11.0, *)) {
SFSafariViewControllerConfiguration *config = [[SFSafariViewControllerConfiguration alloc] init];
config.barCollapsingEnabled = NO;
sfcontroller = [[SFSafariViewController alloc] initWithURL:url configuration: config];
} else {
// Fallback on earlier versions
}
sfcontroller.delegate = self;
[controller presentViewController:sfcontroller animated:YES completion:nil];
In ios 11, they introduce SFSafariViewControllerConfiguration, and by default the barCollapsingEnabled is true and it seems the one that causing my blank SafariView. Hope this solves yours too
We had this issue: our URL was https://our-side.com/index.html (an Angular site that would redirect to the /account route). When we removed the index.html, the SFSafariViewController loaded correctly!
The only thing that has worked so far for me is to make the SafariViewController the rootviewcontroller in the following manner -
((XYZAppDelegate *)[UIApplication sharedApplication].delegate).window.rootViewController = self.svc;
[((XYZAppDelegate *)[UIApplication sharedApplication].delegate).window makeKeyAndVisible];
Essentially I'm working with 3 view controllers.
Main view which starts a download. (Webview based which passes the download).
Modal download controller. (Tab based).
Downloader (HCDownload).
In the main view my download gets passed like so:
//Fire download
[activeDL downloadURL:fileURL userInfo:nil];
[self presentViewController:vc animated:YES completion:nil];
activeDL is initialized in viewDidLoad:
activeDL = [[HCDownloadViewController alloc] init];
If I removed the presentViewController, it still downloads, which is fine. Then i tap my Downloads button, it brings up the controller which defines the tabs like so:
center = [[CenterViewController alloc] init];
activeDL = [[HCDownloadViewController alloc] init];
completedDL = [[DownloadsViewController alloc] init];
activeDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Active Downloads"
image:nil //[UIImage imageNamed:#"view1"]
tag:1];
completedDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Completed Downloads"
image:nil //[UIImage imageNamed:#"view3"]
tag:2];
[self setViewControllers:[NSArray arrayWithObjects:activeDL, completedDL, nil]];
However, it is not passing the current active download. I don't know if it's a initialization problem, or my tab issue of showing the current download.
From his github, he suggests to get the current number of downloads is to call: dlvc.numberOfDownloads which for me would be
[activeDL numberOfDownloads].
I call this in the the Downloader viewWillAppear but nothing shows.
Does anybody has any suggestions or have worked with this controller?
Any help would be appreciated.
When you call:
activeDL = [[HCDownloadViewController alloc] init];
You are creating a new download controller, which has its own internal downloads array. This library, as written, has no way to pass this information from one HCDownloadViewController object to another.
Tying downloads to VC's like this will cause problems -- I recommend you rewrite this code to split that apart.
To hack around it, try to create just one HCDownloadViewController object and pass it around.
Ok so with the last comment of the other answer, "Make activeDL a member variable instead of a local variable.", got me Googling and with some tinkering and bug fixing along the way I managed to get it all up and running perfect.
I declared it all in my AppDelegate.
AppDelegate.h
#interface SharedDownloader : HCDownloadViewController <HCDownloadViewControllerDelegate>
+ (id)downloadingView;
#end
AppDelegate.m
static HCDownloadViewController *active;
#implementation SharedDownloader
+ (id)downloadingView {
if (active == nil)
active = [[HCDownloadViewController alloc] init];
return active;
}
#end
Calling to the class for downloading in my main view controller:
-(id)init{
activeDL = [SharedDownloader downloadingView];
return self;
}
//Spot where I fire the download
if (navigationAction.navigationType == WKNavigationTypeLinkActivated) {
//More code here
[activeDL downloadURL:fileURL userInfo:nil];
}
Lastly in my tab bar controller:
-(id)init {
activeDL = [SharedDownloader downloadingView];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
activeDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Active Downloads" image:nil] tag:2];
}
I believe that's all of it. In any case, thanks to Lou Franco for pointing me in the right direction.
I am a newbee in iOS development and recently run into this problem with customized transition in iOS 9.
I have an object conforms to UIViewControllerTransitioningDelegate protocol and implements animationControllerForDismissedController, something like:
#implementation MyCustomizedTransitioningDelegate
#pragma mark - UIViewControllerTransitioningDelegate
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
MyCustomizedTransitionAnimator *animator = [[MyCustomizedTransitionAnimator alloc] init];
animator.presenting = NO;
return animator;
}
#end
And the process that triggers the modal transition is something like:
#implementation MyViewController
#pragma mark - Initializers
+ (MyCustomizedTransitioningDelegate *)modalTransitioningDelegateSingletonInstance;
{
static MyCustomizedTransitioningDelegate *delegateInst = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
delegateInst = [[MyCustomizedTransitioningDelegate alloc] init];
});
return delegateInst;
}
#pragma mark - UIViewController
- (void)dismissViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion;
{
[self prepareForDismissViewControllerAnimated:animated completion:&completion];
[super dismissViewControllerAnimated:animated completion:completion];
}
- (void)prepareForDismissViewControllerAnimated:(BOOL)animated completion:(dispatch_block_t *)completion;
{
self.presentedViewController.modalPresentationStyle = UIModalPresentationCustom;
self.presentedViewController.transitioningDelegate = [[self class] modalTransitioningDelegateSingletonInstance];
}
#end
Since animationControllerForDismissedController method is not called, the MyCustomizedTransitionAnimator is not created, which leads to its animateTransition not called either, which causes unexpected problem in my app. (Sorry for my poor English...)
I am also attaching the screenshot of stack trace for both iOS8 & iOS9.
In iOS 8, animationControllerForDismissedController is called after the stack trace below.
But in iOS9, transitionDidFinish is called somehow in advance, which I guess probably prevent animationControllerForDismissedController being called?
I was wondering if this is an iOS 9 bug or not. Any explanation or work around solution will be greatly appreciated!
I faced the same issue.
I hope this will help someone.
What fixed it for me is to make the object which applies UIViewControllerTransitioningDelegate protocol as variable instance to keep strong relationship with it.
I think because it gets dismissed after the view is presented first time.
I had the same issue.
Turned out I needed to set the delegate on the navigationController of the UIViewController that contains the trigger button.
Having this old code that didn't work:
UIViewController *dvc = [self sourceViewController];
TransitionDelegate *transitionDelegate = [TransitionDelegate new];
dvc.modalPresentationStyle = UIModalPresentationCustom;
dvc.transitioningDelegate = transitionDelegate;
[dvc dismissViewControllerAnimated:YES completion:nil];
I changed the first line to:
UIViewController *dvc = [self sourceViewController].navigationController;
and it worked.
Hope this helps.
You need to say something like:
MyDestinationViewController *viewController = [[MyDestinationViewController alloc] init];
MyCustomizedTransitioningDelegate *transitioningDelegate = [[MyCustomizedTransitioningDelegate alloc]init];
viewController.transitioningDelegate = transitioningDelegate;
viewController.modalPresentationStyle = UIModalPresentationCustom;
[self presentViewController: viewController animated:YES completion:nil];
Or if you're using segues, in prepareForSegue say something like:
MyDestinationViewController *toVC = segue.destinationViewController;
MyCustomizedTransitioningDelegate *transitioningDelegate = [[MyCustomizedTransitioningDelegate alloc]init];
toVC.transitioningDelegate = transitioningDelegate;
I am using below code for popover for iPad apps. Its working fine for iPad apps but when i use same code for iPhone its getting crash near
" UIPopoverController* removeDefaultPopover=[[UIPopoverController alloc]initWithContentViewController:NavController];"
Can any help me to come out of this..
UIViewController *removeDefaultController = [[UIViewController alloc] init];
UIView *removeDefaultView = [[UIView alloc] init];
removeDefaultController.view = removeDefaultView;
removeDefaultController.contentSizeForViewInPopover = CGSizeMake(100, 100);
UINavigationController *NavController=[[UINavigationController alloc]initWithRootViewController:removeDefaultController];
UIPopoverController* removeDefaultPopover=[[UIPopoverController alloc]initWithContentViewController:NavController];
UIBarButtonItem *edit = [[UIBarButtonItem alloc] initWithTitle:#"EDIT" style:UIBarButtonItemStyleBordered target:self action:#selector(editDefaultLanguage)];
[removeDefaultController.navigationItem setRightBarButtonItem:edit animated:YES];removeDefaultPopover.delegate=self;
[removeDefaultPopover presentPopoverFromRect:CGRectMake(0, 0, 100, 100) inView:self.view permittedArrowDirections:NO animated:YES];
removeDefaultView.backgroundColor=[UIColor redColor];
From the documentation:
Popover controllers are for use exclusively on iPad devices.
Attempting to create one on other devices results in an exception.
You have to implement similar functionality by yourself.
According to Mark Sands UIPopoverController contains the following code:
- (id)initWithContentViewController:(UIViewController *)viewController {
if (([[UIDevice currentDevice] respondsToSelector:#selector(userInterfaceIdiom)]) {
if ([[UIDevice currentDevice] userInterfaceIdiom] != UIUserInterfaceIdiomPad) {
if ([UIPopoverController _popoversDisabled]) {
[NSException raise:NSInvalidArgumentException format:#"-[UIPopoverController initWithContentViewController:] called when not running under UIUserInterfaceIdiomPad."];
}
}
}
...
}
+ (BOOL)_popoversDisabled {
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
if ([bundleIdentifier isEqualToString:#"com.apple.iBooks"] || [bundleIdentifier isEqualToString:#"com.apple.mobilesafari"] ||
[bundleIdentifier isEqualToString:#"com.apple.itunesu"] || [bundleIdentifier isEqualToString:#"com.apple.Maps"]) {
return NO;
}
return YES;
}
As you can see UIPopoverController is enabled for Apple's applications.
You can create UIPopoverController subclass and implement + _popoversDisabled in the following way:
+ (BOOL)_popoversDisabled {
return NO;
}
Or use method swizzling for it.
You can't use "UIPopOverController" for iPhone apps as this is intended for iPad devices only. Alternatively you can use CMPopTipView which is work in similar way as UiPopoverController does. You can check this control on https://github.com/chrismiles/CMPopTipView
I have used this control in my previous app and it works great.
As mentioned previously, UIPopoverController is not currently enabled in iPhone applications. However, if you would still like the functionality I would recommend WEPopover. Here's how easy it is to use:
SettingsViewController *viewController = [[SettingsViewController alloc]init];
self.popover = [[WEPopoverController alloc]initWithContentViewController:viewController];
[self.popover presentPopoverFromRect:self.settingsButton.frame
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
All the correct lifecycle methods are called and it has several convenience methods as well for presenting, dismissing, etc. I've used it in several projects and have been very satisfied with it.
The only thing to watch out for when using this is to make sure that you keep a strong reference to your popover object (thus, in my example you see me use self.popover instead of an instance variable).
I have been developing in Objective-C for two months, so I am quite new to this language and iOS environment. I am updating to iOS7 an app that is working fine for iOS6.
I am getting the next error when a modal view with a web view inside is presented, only in iOS7 and this is working in iOS6. There is a URL request inside but I cannot find what is causing the error.
'-[__NSMallocBlock__ absoluteURL]: unrecognized selector sent to instance 0x16e8b020'
This is the viewWillAppear method on the modal view controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.webView.request) {
//THE NEXT LINE THROWS THE ERROR
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:self.initialURL];
[self.webView loadRequest:req];
}
}
Maybe I am doing something silly but really now I do not know where to look at.
If anyone has experienced something like that before, I will appreciate some help. Thanks in advance.
EDIT:
#interface MyViewController ()
#property (copy, nonatomic) NSURL *initialURL;
#end
#implementation MyViewController
- (id)initWithURL:(NSURL *)initialURL
{
self = [super init];
if (self) {
_initialURL = initialURL;
_webView = [[UIWebView alloc] init];
_webView.backgroundColor = [UIColor clearColor];
_webView.opaque = NO;
_webView.delegate = self;
[self.view addSubview:_webView];
self.modalPresentationStyle = UIModalPresentationFormSheet;
self.view.backgroundColor = [UIColor whiteColor];
}
return self;
}
Method call:
self.modalWebViewController = [[[MyViewController alloc] initWithURL:url] autorelease];
I assume that iOS calls absoluteURL on the self.initialURL object passed to the initWithURL: method. However, the object receiving this message is an NSMallocBlock, so there seems to be something wrong. I assume that your self.initialURL object should be of type NSURL. If so, this would indicate a memory management problem causing the pointer of self.initalURL to point to somewhere else in memory (not to the object you want it to point to).
You could try to run your app with NSZombiesEnabled which prevents any objects from being actually deallocated and instead warns you if a deleted object is still accessed.
You can activate NSZombies in the scheme to run your app (click on the name of your app in Xcode's toolbar on the upper right and choose "Edit Scheme..." from the pop-up menu). In the run-configuration in the "Diagnostics" tab there is a checkbox for activating Zombie objects.