iOS10 SFSafariViewController not working when alpha is set to 0 - objective-c

I'm using SFSafariViewController to grab user's cookie in my app. Here's is my code:
SFSafariViewController *safari = [[SFSafariViewController alloc]initWithURL:[NSURL URLWithString:referrerUrl] entersReaderIfAvailable:NO];
safari.delegate = self;
safari.modalPresentationStyle = UIModalPresentationOverCurrentContext;
safari.view.alpha = 0.0;
safari.view.hidden = true;
[self dismissViewControllerAnimated:false completion:nil];
NSLog(#"[referrerService - StoreViewController] presenting safari VC");
[self presentViewController:safari animated:false completion:nil];
This works well on iOS 9. but on iOS 10 it seems that the SF controller doesn't work (it also block my current context - which happens to be another UIWebView).
Anyone can suggest of an alternative way to hide a SFSafariViewController?

Updated answer:
Apple prohibits this kind of SafariViewController usage in last version of review guidelines:
SafariViewContoller must be used to visibly present information to users; the controller may not be hidden or obscured by other views or layers. Additionally, an app may not use SafariViewController to track users without their knowledge and consent.
Old answer:
In iOS 10 there are some additional requirements for presented SFSafariViewController:
1) Your view should not be hidden, so hidden should be set to NO
2) The minimum value for alpha is 0.05
3) You need to add controller manually with addChildViewController: / didMoveToParentViewController: (callback's doesn't called otherwise).
4) UIApplication.keyWindow.frame and SFSafariViewController.view.frame should have non-empty intersection (in appropriate coordinate space), that means:
safari view size should be greater than CGSizeZero
you can't place safari view off the screen
but you can hide safari view under your own view
Code example:
self.safariVC = [[SFSafariViewController alloc] initWithURL:referrerUrl];
self.safariVC.delegate = self;
self.safariVC.view.alpha = 0.05;
[self addChildViewController:self.safariVC];
self.safariVC.view.frame = CGRectMake(0.0, 0.0, 0.5, 0.5);
[self.view insertSubview:self.safariVC.view atIndex:0];
[self.safariVC didMoveToParentViewController:self];
Also, don't forget to remove safariVC properly after the end of the usage:
[self.safariVC willMoveToParentViewController:nil];
[self.safariVC.view removeFromSuperview];
[self.safariVC removeFromParentViewController];

New info: Don't do this.
The revised App Store guidelines prohibit this practice.
https://developer.apple.com/app-store/review/guidelines/
I'll leave this below for posterity:
I call the following code from my app delegate's didFinishLaunchingWithOptions.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self checkCookie];
}
- (void)checkCookie
{
NSURL *url = [NSURL URLWithString:#"http://domainToCheck.com"];
SFSafariViewController *vc = [[SFSafariViewController alloc] initWithURL:url];
vc.delegate = self;
UIViewController *windowRootController = [[UIViewController alloc] init];
self.secondWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.secondWindow.rootViewController = windowRootController;
[self.secondWindow makeKeyAndVisible];
[self.secondWindow setAlpha:0.1];
[windowRootController presentViewController:vc animated:NO completion:nil];
self.window.windowLevel = 10;
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(nonnull NSDictionary<NSString *,id> *)options
{
[self.secondWindow.rootViewController dismissViewControllerAnimated:NO completion:nil];
self.secondWindow = nil;
self.window.windowLevel = 0;
return YES;
}

I was able to achieve this affect using invisible child view controller:
_safariViewController = [[SFSafariViewController alloc] initWithURL:_requestURL];
_safariViewController.delegate = self;
_safariViewController.view.userInteractionEnabled = NO;
_safariViewController.view.alpha = 0.0;
[_containerViewController addChildViewController:_safariViewController];
[_containerViewController.view addSubview:_safariViewController.view];
[_safariViewController didMoveToParentViewController:_containerViewController];
_safariViewController.view.frame = CGRectZero;

its possible to use this on swift 3 for xcode 8.3.2 becausa i get all i delegate anduse correctly , but i have this error
[12:05]
2017-04-25 12:05:03.680 pruebaslibrary[7476:136239] Warning: Attempt to present on whose view is not in the window hierarchy!

Related

Initiate self.window in appDelegate init method

I'm a web developer creating an Apache Cordova application so my knowledge with Objective-C is very little. Everything is going fine until i try to supplement the splash screen with a video. It sort of does it, but not fully.. It starts with displaying the Default.png followed by the SplashScreenLoader. It then actually plays the video and I know this because the audio is emitted, but the video layer isn't shown.
What I've found out is that the self.window or self.viewController are both defined in didFinishLaunchingWithOptions, so they don't exist in the - (id) init method. Therefore I can't find a way to place it on top of the loading splash.
My init method currently looks like this in AppDelegate.m:
- (id) init {
NSString *moviePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Splash_v1.mp4"];
NSURL *movieURL = [NSURL fileURLWithPath:moviePath];
MPMoviePlayerController* moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL: movieURL];
moviePlayer.controlStyle = MPMovieControlStyleNone;
[moviePlayer.view setFrame: self.window.bounds];
[self.window addSubview:moviePlayer.view];
[self.window makeKeyAndVisible];
[moviePlayer play];
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
[cookieStorage setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
[CDVURLProtocol registerURLProtocol];
return [super init];
}
Here the self.window is null, and I've also attempted to set the self.window with this code:
CGRect screenBounds = [[UIScreen mainScreen] bounds];
self.window = [[[UIWindow alloc] initWithFrame:screenBounds] autorelease];
...without prevail. It actually sets it, but for the subsequent code it doesn't wanna do it.
So what I'm wondering is, how would I place this video on top of the splash's content, before didFinishLaunchingWithOptions kicks in?
Thanks in advance,
//Peter
So what I'm wondering is, how would I place this video on top of the splash's content, before didFinishLaunchingWithOptions kicks in?
actually, you do that in didFinishLaunchingWithOptions. put this statements in there (in the bolierplate code that Xcode generates for you, you should already have a call to makeKeyAndVisible, so just complement it):
[self.window addSubview:moviePlayer.view];
[self.window makeKeyAndVisible];
having previously instantiated your moviePlayer.
One way to avoid the blank screen could be this:
create a UIImageView containing your Default.png image;
display such UIImageView by adding it to your self.window as a subview (this will create no black screen effect);
initialize your player (I assume it takes some time, hence the black screen) and add it below the UIImageView;
when the player is ready (viewDidLoad) push it on top of the UIImageView.
Finally, I don't know how your player will signal the end of the video play, but I assume you have some delegate method; make you appDelegate be also your player delegate and from there, remove UIImageView and player from self.window and add you other view to self.window.
Hope this helps.
EDIT:
This is a rough sketch of what I would try and do in your app delegate appDidFinishLaunching:
self.moviePlayer = <INIT MOVIEW PLAYER CONTROLLER>
self.backgroundImage = <INIT UIImageView with Default.png>
[self.window addSubview:self.moviePlayer.view];
[self.window addSubview:self.backgroundImage];
[self.window makeKeyAndVisible];
In your movie Player viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
...
[self.view.superview addSubview:self.view]; //-- this will just move the player view to the top
...
}
If that helps anyone else, I had the same issue which was resolved by moving '[self.window makeKeyAndVisible];' above the subview like this, so in appdelegate.m:
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 300)];
myView.backgroundColor = [UIColor blueColor];
[self.window makeKeyAndVisible];
[self.window addSubview:myView];
This works fine, but if I swap the last two statements around, the subview does not display.

QLPreviewController only shows a single file

I am using a QLPreviewController to display a set of files. However, it only shows the first one and I can't seem to swipe or do anything to show the second. What am I doing wrong? Do I have to set it manually? If so - how would I go about doing that?
This is from my AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// normal viewcontroller init here
[self showPreview] ;
return YES;
}
NSArray* documents ;
QLPreviewController* preview ;
- (void) showPreview
{
documents = [[NSArray alloc] initWithObjects: #"photo" , #"photo2" , nil ] ;
preview = [[QLPreviewController alloc] init];
preview.dataSource = self;
preview.delegate = self;
preview.view.frame = [[UIScreen mainScreen] bounds];
//save a reference to the preview controller in an ivar
// self.previewController = preview;
//refresh the preview controller
[preview reloadData];
[[preview view] setNeedsLayout];
[[preview view] setNeedsDisplay];
[preview refreshCurrentPreviewItem];
preview.view.userInteractionEnabled = YES;
//add it
[self.viewController.view addSubview:preview.view];
}
I also declared the two callback functions in the same AppDelegate.m file:
- (id <QLPreviewItem>) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index
{
NSString* filename = [documents objectAtIndex:index] ; // #"photo" ;
NSURL* returnURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource: filename ofType:#"jpg" ]] ;
return returnURL ;
}
- (NSInteger) numberOfPreviewItemsInPreviewController: (QLPreviewController *) controller
{
return [documents count];
}
You are displaying it wrong.
QLPreviewController is a UIViewController, which means you basically have 2 ways of displaying it:
Push it into your UINavigationController.
Display it modally (this can be done with or without a UINavigationController - depends if you want a navigation bar).
If you choose option 2 you get "free" navigation arrows to switch between items.
For option 1 you need to create the arrows yourself.
This following is taken from the QLPreviewController documentation:
If there is more than one item in the list, a modally-presented (that
is, full-screen) controller displays navigation arrows to let the user
switch among the items. For a Quick Look preview controller pushed
using a navigation controller, you can provide buttons in the
navigation bar for moving through the navigation list.

Seamlessly flip from one modal view to another, without showing a solid colour background

I have the following UI for an iPad app:
When I click on the Settings button, I want the dialog to horizontally flip to show the settings dialog.
I have this working fine. But, there is a background colour shown when the dailog flips over. As you can see:
Is there any way to not have this block of colour be visible as the dialogs flip? I'd like it to look more seamless -- as if it's a sheet of paper flipping over.
The views are essentially this:
Window
Main View. Set to the window's rootViewController
Login modal view
Thus the main window and root controller are setup as follows (in the app delegate class):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[MainViewController alloc] initWithNibName:#"MainView" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
The login window is setup and shown in the main view's viewDidAppear:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Setup and show Login dialog
LoginViewController* controller = [[LoginViewController alloc] initWithNibName:#"LoginView" bundle:nil];
controller.delegate = self;
controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
controller.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:controller animated:YES];
}
And when the Settings button is pressed: showing the Settings modal view is done in pretty much the same way that the Login modal view was shown:
- (IBAction)settingsButtonPressed:(id)sender {
SettingsViewController *controller = [[SettingsViewController alloc] initWithNibName:#"SettingsView" bundle:nil];
controller.delegate = self;
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
controller.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:controller animated:YES];
}
I don't think there's any way to do what you want using the modalPresentationStyle. You'll need to implement the animation yourself using a transition animation using the following method:
+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion
With the UIViewAnimationOptionTransitionFlipFromLeft option.
In this case the new view you want to flip is not the content of the modal (the controller.view) but the modal frame itself, so experiment with just calling the method above from your settings button, and instead of passing controller.view, substitute controller.view.superview, and if that doesn't work, try controller.view.superview.superview until the animation looks right.
It will require some tweaking to work out exactly how to do it.

ModalViewController for Single Subview

Ok, so bear with me: as this is an Objective-C related question, there's obviously a lot of code and subclassing. So here's my issue. Right now, I've got an iPad app that programmatically creates a button and two colored UIViews. These colored UIViews are controlled by SubViewControllers, and the entire thing is in a UIView controlled by a MainViewController. (i.e. MainViewController = [UIButton, SubViewController, SubViewController])
Now, all of this happens as it should, and I end up with what I expect (below):
However, when I click the button, and the console shows "flipSubView1", nothing happens. No modal view gets shown, and no errors occur. Just nothing. What I expect is that either subView1 or the entire view will flip horizontally and show subView3. Is there some code that I'm missing that would cause that to happen / is there some bug that I'm overlooking?
viewtoolsAppDelegate.m
#implementation viewtoolsAppDelegate
#synthesize window = _window;
#synthesize mvc;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
mvc = [[MainViewController alloc] initWithFrame:self.window.frame];
[self.window addSubview:mvc.theView];
[self.window makeKeyAndVisible];
return YES;
}
MainViewController.m
#implementation MainViewController
#synthesize theView;
#synthesize subView1, subView2, subView3;
- (id)initWithFrame:(CGRect) frame
{
theView = [[UIView alloc] initWithFrame:frame];
CGRect sV1Rect = CGRectMake(frame.origin.x+44, frame.origin.y, frame.size.width-44, frame.size.height/2);
CGRect sV2Rect = CGRectMake(frame.origin.x+44, frame.origin.y+frame.size.height/2, frame.size.width-44, frame.size.height/2);
subView1 = [[SubViewController alloc] initWithFrame:sV1Rect andColor:[UIColor blueColor]];
subView2 = [[SubViewController alloc] initWithFrame:sV2Rect andColor:[UIColor greenColor]];
subView3 = [[SubViewController alloc] initWithFrame:sV1Rect andColor:[UIColor redColor]];
[theView addSubview:subView1.theView];
[theView addSubview:subView2.theView];
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[aButton addTarget:self action:#selector(flipSubView1:) forControlEvents:(UIControlEvents)UIControlEventTouchDown];
[aButton setFrame:CGRectMake(0, 0, 44, frame.size.height)];
[theView addSubview:aButton];
return self;
}
- (void)flipSubView1:(id) sender
{
NSLog(#"flipSubView1");
[subView3 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[subView1 presentModalViewController:subView3 animated:YES];
}
SubViewController.m
#implementation SubViewController
#synthesize theView;
- (id)initWithFrame:(CGRect)frame andColor:(UIColor *)color
{
theView = [[UIView alloc] initWithFrame:frame];
theView.backgroundColor = color;
return self;
}
TLDR: modal view not working. should see flip. don't.
It doesn't look like you're setting the 'view' property of the MainViewController anywhere, just 'theView'. The controllers view delegate must be connected to the root view it displays for it to work properly. You'll need to correct that on the Sub View Controller impl as well. If you want all the plumbing that framework classes bring, you have to set things up the way they expect.
Also, you're calling presentModalViewController on one of the sub view controllers; change that to call [self presentModalViewController:...], since the MainViewController is the one which will 'own' the modal view.
I think if you fix those points, you'll find -presentModalViewController will work.

Second UIWindow Not Displaying (iPad)

I am attempting to create two UIWindows because I would like two UINavigationControllers on screen at the same time on my app. I initialize two windows in my app delegate but only one window's view is displayed. Does anyone know why this is so?
Here is the code I used:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIViewController * controller1 = [[UIViewController alloc] init];
[controller1.view setBackgroundColor:[UIColor grayColor]];
UINavigationController * nav1 = [[UINavigationController alloc] initWithRootViewController:controller1];
[window addSubview:nav1.view];
[window makeKeyAndVisible];
UIWindow * window2 = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
UIViewController * controller2 = [[UIViewController alloc] init];
[controller2.view setBackgroundColor:[UIColor yellowColor]];
UINavigationController * nav2 = [[UINavigationController alloc] initWithRootViewController:controller2];
[window2 addSubview:nav2.view];
[window2 makeKeyAndVisible];
NSLog(#"%#", [[UIApplication sharedApplication] windows]);
return YES;
}
The gray from the first window is visible, but the yellow from the second is not. The output from this is:
"<UIWindow: 0x591e650; frame = (0 0; 768 1024); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x591e7a0>>",
"<UIWindow: 0x5923920; frame = (0 0; 100 100); layer = <CALayer: 0x59239a0>>"
which means the second window is created and added to the application, but just not displayed. Does anyone know why this is so?
Thanks in advance!
The two UIWindow's windowLevel property is equal, they are all UIWindowLevelNormal.
If you want the second UIWindow display font of the first UIWindow, You should set the second UIWindow's windowLevel value bigger. Like:
window2.windowLevel = UIWindowLevelNormal + 1;
PS:
[window makeKeyAndVisible];
...
[window2 makeKeyAndVisible];
There is only one keyWindow at a time, The key window is the one that is designated to receive keyboard and other non-touch related events. Only one window at a time may be the key window.
Just use a UISplitViewController.
Or try MGSplitVIewController if you need to customization. It might have what you need.
I've discovered how to get the second UIWindow to display. You must set the clipsToBound property to YES. Otherwise, the view from one of the windows will completely cover the other view. The two windows were properly added and visible after all.
This might be a really old post but I just run into the same problem. Some coding mistakes where already answered but the main issue we have here is how you instantiating the UIWindow.
Here is a Swift example how to display another UIWindow correctly.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? = UIWindow(frame: UIScreen.mainScreen().bounds)
let newWindow = UIWindow(frame: UIScreen.mainScreen().bounds)
// save a reference to your Window so it won't be released by ARC
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.window!.rootViewController = SomeViewController()
self.window!.makeKeyAndVisible()
// in your example you have created the window inside this method,
// which executes correctly and at the end of this method just releases the window,
// because you never saved the reference to the window
self.newWindow.rootViewController = SomeOtherViewController()
self.newWindow.windowLevel = UIWindowLevelStatusBar + 1.0
self.newWindow.hidden = false
return true
}
}
Btw. you don't have to create a UIWindow in AppDelegate. It depends on your code behavior.
try this code...
id delegate = [[UIApplication sharedApplication] delegate];
[[delegate FirstView] presentModalViewController:SecondView animated:YES];