View not receiving touches after custom spinner is invoked - objective-c

I followed the guide here to make my own custom view pop up to indicate I am searching for the users results:
http://joris.kluivers.nl/blog/2012/03/02/custom-popups-revisited/
When the search results are found, I dismiss the custom view, an a Flurry Ad pops up immediately. I click the x button on the ad and my next view appears. This view is not receiving any touch events anymore, neither is the tab bar at the bottom working. I suspect it has to do something with the custom view still animating away(via a transform to 0.0f) when the Flurry ad is coming up, but I am not sure. I am including the code for how the spinner is created, as well as the dismissal.
Note: I have confirmed the problem is related to Flurry Ads showing up modally while my customSpinner is going away. I don't know how to fix it though. When I don't display the Ad, the touch events are registering fine.
PCFFinder.c
- (void)queryServer:(NSString *)queryString {
__block PCFCustomSpinner *spinner = [[PCFCustomSpinner alloc] initWithFrame:CGRectMake(0, 0, 200, 200):#"Searching.."];
[spinner show];
NSString *url = #"https://selfservice.mypurdue.purdue.edu/prod/bwckschd.p_get_crse_unsec";
NSString *referer = #"https://selfservice.mypurdue.purdue.edu/prod/bwckgens.p_proc_term_date";
dispatch_queue_t task = dispatch_queue_create("Task 3", nil);
dispatch_async(task, ^{
NSString *webData = [PCFWebModel queryServer:url connectionType:#"POST" referer:referer arguements:queryString];
if (webData) {
classesOffered = [PCFWebModel parseData:webData type:2];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner dismiss];
spinner = NULL;
if ([classesOffered count] > 0) {
if (self.view.window)
searching = NO;
//the ad pops up in the next view controllers viewWillAppear method and after exiting the ad, the views no longer work
[self performSegueWithIdentifier:#"ChooseClass" sender:self];
}else {
[spinner dismiss];
PCFCustomAlertView *alert = [[PCFCustomAlertView alloc] initAlertView:CGRectMake(0, 0, 300, 200) :#"Search Results" :#"No results were found. Please try broadening the search or double check your input." :#"OK"];
[alert show];
//the views still work here though..
searching = NO;
}
});
//rest omitted because it is irrelevant.
}
CustomPopup.c
-(void)dismiss
{
__block UIWindow *animatedWindow = self.window;
[UIView animateWithDuration:0.3f delay:0.0f options:UIViewAnimationCurveEaseOut animations:^{
animatedWindow.transform = CGAffineTransformMakeScale(0.0f, 0.0f);
}completion:^(BOOL finished) {
animatedWindow.hidden = YES;
animatedWindow = nil;
}];
}
-(void)show
{
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.windowLevel = UIWindowLevelAlert;
self.window.backgroundColor = [UIColor clearColor];
self.center = CGPointMake(CGRectGetMidX(self.window.bounds), CGRectGetMidY(self.window.bounds));
[self.window addSubview:self];
[self.window makeKeyAndVisible];
//animated
self.window.transform = CGAffineTransformMakeScale(1.5f, 1.5f);
self.window.alpha = 0.0f;
__block UIWindow *animationWindow = self.window;
[UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationCurveEaseIn animations:^{
animationWindow.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
animationWindow.alpha = 1.0f;
}completion:nil];
}
I have tried setting the view as first responder in the viewWillAppear method, but that doesn't work either.

In your dismiss method you are setting the single window to have a zero scale and hidden. This is surely your problem. Animate the pop ups view then remove it from the super view instead.

Related

UIWebView partially operable

I've got a very strange problem with a UIWebView:
I have a function in my appdelegate which opens a UIWebView to show my privacy policy. The function gets called twice from two different views. Once if the user clicks a button on the login screen and once if the user clicks a button on a view, which lies on top a google map view.
When the function is called from the login view everything works fine and I can , but when the function is called from the other view, the UIWebView appears but is not complete operable (for testing I load google.com):
I cannot scroll and I cannot open most of the links, the only elements, which respond to touch events are marked in this picture:
And here comes my code:
In my appdelegate I have:
- (void)showPrivacy:(UIView *)view
{
overlay = [[UIView alloc] initWithFrame:view.frame];
overlay.backgroundColor = [UIColor blackColor];
overlay.alpha = 0;
[view addSubview:overlay];
privacyView = [[WPPrivacyView alloc] initWithFrame:CGRectMake(15, 40, view.frame.size.width-30, view.frame.size.height-75)];
privacyView.layer.shadowColor = [UIColor blackColor].CGColor;
privacyView.layer.shadowOffset = CGSizeMake(0, 1);
privacyView.layer.shadowOpacity = 0.75;
privacyView.layer.shadowRadius = 20.0f;
privacyView.userInteractionEnabled = YES;
privacyView.scrollView.scrollEnabled = YES;
privacyView.scrollView.bounces = NO;
privacyView.scalesPageToFit = YES;
[view addSubview:privacyView];
[UIView animateWithDuration:0.1 animations:^{
privacyView.alpha = 1;
}];
privacyView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.1, 0.1);
CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:#"transform.scale"];
bounceAnimation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.1],
[NSNumber numberWithFloat:1.1],
[NSNumber numberWithFloat:0.95],
[NSNumber numberWithFloat:1.0], nil
];
bounceAnimation.duration = 0.3;
bounceAnimation.removedOnCompletion = NO;
[privacyView.layer addAnimation:bounceAnimation forKey:#"bounce"];
privacyView.layer.transform = CATransform3DIdentity;
}
To hide the View I call:
- (void)hidePrivacy
{
[UIView animateWithDuration:0.3 animations:^{
privacyView.alpha = 0;
privacyView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.1, 0.1);
} completion:^(BOOL finished) {
[privacyView removeFromSuperview];
}];
}
The showPrivacy function gets called from the login view and from the map view as follows:
WPAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
[appDelegate showPrivacy:self.view];
So why is everything working fine, when the webview appears at the login view and why can I operate SOME elements but not all, when it appears at the map view?
Because you're adding your overlay view as a subview. This means the superview is going to get involved with user interaction and some events will get 'stolen' or blocked. In particular, scrolling, which uses gesture recognizers.
You may want to change your design so that you present a modal view controller with your overlay. If you want the background to be the original view then you could grab an image of it before you present the modal.

Why is my iAd banner not showing?

- (void)viewDidLoad
{
adView = [[ADBannerView alloc] initWithFrame:CGRectZero];
adView.requiredContentSizeIdentifiers = [NSSet setWithObject:ADBannerContentSizeIdentifierPortrait];
adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
CGRect adFrame = adView.frame;
adFrame.origin.y = self.view.frame.size.height-adView.frame.size.height;
adView.frame = adFrame;
[self.view addSubview:adView];
}
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
if (!self.bannerIsVisible)
{
self.bannerIsVisible = YES;
}
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
if (self.bannerIsVisible)
{
self.bannerIsVisible = NO;
}
There are a four things. First, you should be positioning the banner off screen in your viewDidLoad method because it will just show an empty frame when you first launch and will more than likely get rejected because of it.
Secondly, you are setting up your banner view incorrectly. I think the frame is still CGZero. Thirdly you are not setting the bannerView's delegate. Try the following:
-(void)viewDidLoad{
CGRect frame=CGRectZero;
frame.size = [ADBannerView sizeFromBannerContentSizeIdentifier:ADBannerContentSizeIdentifierPortrait];
// Place frame at the bottom edge of the screen out of sight
frame.origin = CGPointMake(0.0, CGRectGetMaxY(self.view.bounds));
// Now to create and configure the banner view
ADBannerView *adView = [[ADBannerView alloc] initWithFrame:frame];
adView.requiredContentSizeIdentifiers = [NSSet setWithObject:ADBannerContentSizeIdentifierPortrait];
// Set the delegate to self, so that we are notified of ad responses
adView.delegate = self;
[self.view addSubview: adView];
}
Fourth, in your bannerViewDidLoadAd: method you are not animating the banner ad into place. Try this:
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
// Get a brand new frame
CGRect newFrame=CGRectZero;
CGPoint frameOrigin=CGPointZero;
// Set the origin
frameOrigin=CGPointMake(0.0, CGRectGetMaxY(self.view.bounds));
newFrame.origin=frameOrigin;
// Set the size
newFrame.size=[ADBannerView sizeFromBannerContentSizeIdentifier:ADBannerContentSizeIdentifierPortrait];
CGFloat bannerHeight = newFrame.size.height;
CGFloat bannerOffset=0.0;
// Determine where the new frame should be
if (!self.bannerIsVisible)
{
// It should be visible, raise it up
bannerOffset=-bannerHeight;
}
CGRect offSetRect=CGRectOffset(newFrame,0.0f,bannerOffset);
[UIView animateWithDuration:0.2
animations:^{banner.view.frame=offSetRect}
completion:^(BOOL finished){
if (bannerOffSet<0){
self.bannerIsVisible=YES;
}else{
self.bannerIsVisible=NO;
}
}
];
}
of course if the banner is supposed to be positioned at the top of the screen, you can probably figure out how things need to be modified, but this gets you in going in the right direction.
Good luck
Not sure by reading your question, but do be aware that Apple simulates iAds not being available. Sometimes you need to try multiple times before the sample ad comes through.

transitionFromView:toView:duration:options:completion: is not animating the transition

I have a view controller that displays the views of 2 sub view controllers in a given area of its view. The 2 sub view controllers are FlopVC and FipVC.
I want to animate the transition from one sub view to the other. The code I'm using is:
-(IBAction)flip:(id)sender{
UIViewController *newVC = nil;
if (self.isFlip) {
newVC = [[FlopVC alloc] initWithNibName:nil bundle:nil];
}else{
newVC = [[FipVC alloc] initWithNibName:nil bundle:nil];
}
newVC.view.frame = CGRectMake(120, 20, 240, 260);
[self.view addSubview:newVC.view];
[UIView transitionFromView:self.currentVC.view
toView:newVC.view
duration:0.9
options:UIViewAnimationTransitionFlipFromLeft
completion:^(BOOL finished) {
self.currentVC = newVC;
self.isFlip = ! self.isFlip;
}];
}
The sub views are swapped, but without any animation. What am I doing wrong?
PS the full project is here.
UIViewAnimationTransitionFlipFromLeft != UIViewAnimationOptionTransitionFlipFromLeft
if you are using the new iOS5 view container paradigm you need to do something along the lines of the following:
-(IBAction)flip:(id)sender{
UIViewController *newVC = nil;
if (self.isFlip) {
newVC = [[FlopVC alloc] initWithNibName:nil bundle:nil];
}else{
newVC = [[FipVC alloc] initWithNibName:nil bundle:nil];
}
newVC.view.frame = CGRectMake(120, 20, 240, 260);
// required for the new viewController container
self.currentVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
[self transitionFromViewController:self.currentVC
toViewViewController:newVC.view
duration:0.9
options: UIViewAnimationOptionTransitionFlipFromLeft
animations:nil
completion:^(BOOL finished) {
// required for the new viewController container
[self.currentVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
self.currentVC=newVC;
}];
}
reference the section Implementing a Container View Controller and the 2011 WWDC videos on UIViewController containers for more info.
Here is working code that (by sheer coincidence) does exactly what you're describing. My two child vc's are stored in self->swappers. The integer cur keeps track of which one is currently showing. The spot in my interface where the subview is to go is marked by a view outlet, panel.
UIViewController* fromvc = [self->swappers objectAtIndex:cur];
cur = (cur == 0) ? 1 : 0;
UIViewController* tovc = [self->swappers objectAtIndex:cur];
tovc.view.frame = self.panel.bounds;
// must have both as children before we can transition between them
[self addChildViewController:tovc]; // "will" called for us
// note: when we call remove, we must call "will" (with nil) beforehand
[fromvc willMoveToParentViewController:nil];
[self transitionFromViewController:fromvc
toViewController:tovc
duration:0.4
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:nil
completion:^(BOOL done){
// note: when we call add, we must call "did" afterwards
[tovc didMoveToParentViewController:self];
[fromvc removeFromParentViewController]; // "did" called for us
}];

Attempting to "turn on/off" different UITapGestureReconginzer methods

I have this lovely nav bar asset:
navBar view http://dl.dropbox.com/u/1734050/ViewWithNavBar.jpg
and when users tap on the middle button (one with the arrows) it will bring up this other lovely "share box" asset
shareBox dropdown from navBar http://dl.dropbox.com/u/1734050/navBarWithShareBox.jpg
What I have in the code so far is that when the screen is tapped, the nav bar will appear, and disappear on another tap. Secondly, when the user taps on the "share" button, the share box comes up, and the user can tap outside of the box to dismiss it.
Here is the problem: I cannot dismiss the nav bar after bringing up the share box!
Here is some code:
-(void)viewDidLoad {
...
...
...
UITapGestureRecognizer *tapNavBar = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapForNavBar:)];
self.tapForNavBar = tapNavBar;
[self.viewForTaps addGestureRecognizer:self.tapForNavBar];
// btw, tapForNavBar is a UITapGestureRecognizer instance variable.
// also, viewForTaps is a view (instance variable) I made to handle
// where the user could tap (most of the screen)
}
#pragma mark -
#pragma mark Tap and Gesture Methods
-(void)handleTapForNavBar:(UIGestureRecognizer *)gestureRecognizer {
if (self.navBarIsHidden) {
[UIView animateWithDuration:0.5 animations:^ {
//self.primeViewController.view.alpha = 0.8;
self.navBar.alpha = 1.0;
}];
self.detailViewButton.userInteractionEnabled = YES;
self.shareButton.userInteractionEnabled = YES;
self.aboutAppButton.userInteractionEnabled = YES;
self.navBarIsHidden = NO;
}
else {
[UIView animateWithDuration:0.5 animations:^ {
self.navBar.alpha = 0.0;
//self.primeViewController.view.alpha = 1.0;
}];
self.detailViewButton.userInteractionEnabled = NO;
self.shareButton.userInteractionEnabled = NO;
self.aboutAppButton.userInteractionEnabled = NO;
self.navBarIsHidden = YES;
}
}
Ok, so that should look all dandy (and works like it too!) -- now here is perhaps where it gets unorthodox?
-(IBAction)showShareMenu:(id)sender {
if (self.navBarShareBoxIsHidden) {
[UIView animateWithDuration:0.5 animations:^ {
self.navBarShareBox.alpha = 1.0;
}];
[self.tapForNavBar removeTarget:nil action:NULL];
UITapGestureRecognizer *tapShareBox = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapForShareBox:)];
self.tapForShareBox = tapShareBox;
[self.viewForTaps addGestureRecognizer:self.tapForShareBox];
self.navBarShareBoxIsHidden = NO;
self.twitterButton.userInteractionEnabled = YES;
self.facebookButton.userInteractionEnabled = YES;
self.googlePlusButton.userInteractionEnabled = YES;
}
else {
[UIView animateWithDuration:0.5 animations:^ {
self.navBarShareBox.alpha = 0.0;
}];
[self.tapForShareBox removeTarget:nil action:NULL];
[self.tapForNavBar addTarget:self action:#selector(handleTapForNavBar:)];
self.navBarShareBoxIsHidden = YES;
self.twitterButton.userInteractionEnabled = NO;
self.facebookButton.userInteractionEnabled = NO;
self.googlePlusButton.userInteractionEnabled = NO;
}
}
I then create this method to handle the specific shareBox tapping:
-(void)handleTapForShareBox:(UIGestureRecognizer *)gestureRecognizer {
if (!self.navBarShareBoxIsHidden) {
[UIView animateWithDuration:0.5 animations:^ {
self.navBarShareBox.alpha = 0.0;
}];
self.navBarShareBoxIsHidden = YES;
self.twitterButton.userInteractionEnabled = NO;
self.facebookButton.userInteractionEnabled = NO;
self.googlePlusButton.userInteractionEnabled = NO;
[self.tapForShareBox removeTarget:nil action:NULL];
[self.tapForNavBar addTarget:self action:#selector(handleTapForNavBar:)];
}
}
I'm assuming my problem is coming from alloc/initing the new UITapGestureRecognizer in the -(IBAction)showShareMenu method. I thought by using the removeTarget... and addTarget messages I could easily tell my code which TapGesture method it should use, but considering it isn't working, I was wrong! Where did I go wrong? If you need more info, I'd be happy to give more.
Ah Ha! Looks like I found a solution! In my handleTapForShareBox method I did this:
-(void)handleTapForShareBox:(UIGestureRecognizer *)gestureRecognizer {
if (!self.navBarShareBoxIsHidden) {
[UIView animateWithDuration:0.5 animations:^ {
self.navBarShareBox.alpha = 0.0;
}];
self.navBarShareBoxIsHidden = YES;
self.twitterButton.userInteractionEnabled = NO;
self.facebookButton.userInteractionEnabled = NO;
self.googlePlusButton.userInteractionEnabled = NO;
[self.tapForShareBox removeTarget:nil action:NULL];
//[self.tapForNavBar addTarget:self action:#selector(handleTapForNavBar:)];
// Here is the new stuff, I added another alloc init of a UITapGestureRecognizer
// and re-assigned it to my UITap instance variable
UITapGestureRecognizer *tapNavBar = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapForNavBar:)];
self.tapForNavBar = tapNavBar;
[self.viewForTaps addGestureRecognizer:self.tapForNavBar];
}
}
It works great now! However, there were a couple things:
1) Do I have to release the UITapGestureRecognizer local pointer I created? Or because I initWithTarget it will be autoreleased?
2) Is this a hack? Or is this a proper way of passing different tap methods around? I'm just a little confused why the removeTarget and addTarget didn't do the trick.

UIAlert partially hidden on iPad horizontal orientation. How to move it up?

In my iPad app, I have an implementation of a login window as a UIAlertView subclass that adds two UITextField, using an idiom that's found on the net.
The problem is that in horizontal orientation, the alert is partially hidden by the keyboard. Namely, the buttons are hidden. Of course, it's possible to hide the keyboard to reveal the button, but this is ugly.
The way the idiom is supposed to fix that is to add a translation transform to the view:
CGAffineTransform translate = CGAffineTransformMakeTranslation(0.0, 100.0);
[self setTransform:translate];
However that doesn't really work:
in initial vertical orientation, that works (but stops working after any device rotation)
in initial horizontal orientation, one can see it work, but the alert is right away animated back to the center of the screen where it's partially hidden again.
in any orientation after rotating the iPad, it doesn't work at all: nothing happens, as if the transform was not even there.
(additionally, this transform idea may stop {working|being necessary} for iOS 4.x. But that's another issue).
Any idea welcome.
For the sake of completeness, here is the full code:
- (id)initWithLogin:(NSString *)defaultLogin delegate:(id)delegate
{
if (self = [super initWithTitle:#"Username and password"
message:#"\n\n\n"
delegate:delegate
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Enter", nil])
{
UITextField *theTextField = [[UITextField alloc] initWithFrame:CGRectMake(12.0, 45.0, 260.0, 25.0)];
[theTextField setBackgroundColor:[UIColor whiteColor]];
theTextField.text = defaultLogin;
theTextField.placeholder = #"username";
[self addSubview:theTextField];
self.loginField = theTextField;
[theTextField release];
theTextField = [[UITextField alloc] initWithFrame:CGRectMake(12.0, 80.0, 260.0, 25.0)];
[theTextField setBackgroundColor:[UIColor whiteColor]];
theTextField.placeholder = #"password";
theTextField.secureTextEntry = YES;
[self addSubview:theTextField];
self.passwordField = theTextField;
[theTextField release];
// the two next lines may not be useful for iOS > 4.0
CGAffineTransform translate = CGAffineTransformMakeTranslation(0.0, 100.0);
[self setTransform:translate];
}
return self;
}
Thanks for Bittu for a working solution. Here is my implementation of his idea. I put the code to move the alert up in a new method of my subclass:
- (void)slideUp
{
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.25f];
CGPoint center = self.center;
center.y -= 100;
self.center = center;
[UIView commitAnimations];
}
The key point is when to call that code. There are two cases: the simplest case is when the user rotates the device. Unfortunately, the Alert class is not informed of that event, only the client UIViewController, so that's were we need to call it. This is ugly by breaking encapsulation, but so be it:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
if(loginWindow && UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
[loginWindow slideUp];
}
}
The second case is when the orientation is already horizontal when opening up the alert. The alert delegate is informed of that in its didPresentAlertView: delegate method.
- (void)didPresentAlertView:(UIAlertView *)alertView
{
if ( UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ) {
[self slideUp];
}
}
Unfortunately, that implementation doesn't work, because calling slideUp at that time will conflict with the system already animating the alert to the center of the screen. The solution is to delay the call slightly, for example using NSTimer:
- (void)didPresentAlertView:(UIAlertView *)alertView
{
if ( UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ) {
[NSTimer scheduledTimerWithTimeInterval:0.25f
target:self
selector:#selector(slideUp)
userInfo:nil
repeats:NO];
}
}
By the way, slideUp doesn't have the documented signature for a NSTimer selector, but it still seems to work! If that bothers you, simply add an in between method with the correct signature:
- (void)slideUpByTimer:(NSTimer*)theTimer
{
[self slideUp];
}
I had the exact same issue with the alertView with UITextField as a subview. Instead of taking the transform path, I took a little different approach. Here's how I did it.
First create your alertview with textfield in it:
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(12.0, 40.0, 260.0, 25.0)];
[textField setBackgroundColor:[UIColor whiteColor]];
textField.placeholder = #"Search";
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Enter Search Text" message:#" " delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Enter",nil];
[alertView addSubview:textField];
[alertView show];
Then in the UIViewController that's going to show the alert view, implement this method:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
if(self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft || self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.25f];
CGPoint center = self.alertView.center;
center.y -= 100;
self.alertView.center = center;
[UIView commitAnimations];
}
}
this method happens after the "rotation finished" notification has been posted. therefore, you'll see the alertview partially under the keyboard before the animation to slide it up starts happening. Hope this helps you with your login alertview.