Why doesn't UITapGestureRecognizer work in subview of keyWindow (full screen)? - ios

I asked here (UIView added as an UIWindow subview doesn't respond to taps) but I thought it deserved its own question.
If I set up a subclassed UIView (CustomView) this way, I can't get gestures to work within the view:
CustomView *customView = [[[CustomView alloc] initWithFrame:[UIApplication sharedApplication].keyWindow.frame];
customView.frame = [UIApplication sharedApplication].keyWindow.bounds; // leaves space at bottom of screen if not here
[[UIApplication sharedApplication].keyWindow addSubview:customView];
But if I set up this way, gestures work fine:
CustomView *customView = [[[CustomView alloc] initWithFrame:[UIApplication sharedApplication].keyWindow.frame];
customView.frame = [UIApplication sharedApplication].keyWindow.bounds; // leaves space at bottom of screen if not here
[[UIApplication sharedApplication].windows[0] addSubview:customView];
Note that using .windows[0] instead of .keyWindow means the status bar is still there, so I have to hide that when I unhide customView to get an empty full screen.
Within customView:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
id mainView;
if (self)
{
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"CustomNib" owner:self options:nil];
mainView = [subviewArray objectAtIndex:0];
}
return mainView;
}
And here's the gesture setup, also within customView:
UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[self addGestureRecognizer:singleFingerTap];
Any ideas why the gesture recognizer doesn't work when the view is a subview of .keyWindow but works when the view is a subview of .windows[0]?

In initWithFrame, you are returning a UIView --- not an instance of CustomView.
You probably want your CustomView code to look like this:
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
UIView *mainView;
if (self)
{
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"CustomNib" owner:self options:nil];
mainView = [subviewArray objectAtIndex:0];
mainView.frame = self.bounds;
mainView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:mainView];
[self setupTap];
}
return self;
}
- (void) setupTap {
UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[self addGestureRecognizer:singleFingerTap];
}
- (void)handleTap:(UIGestureRecognizer *)g {
NSLog(#"Tap in CustomView!");
}
#end

Related

UISwipeGestureRecognizer Not Performing Method

I am having a difficult time getting a Swipe Gesture Recognizer to work on my app. Here is the Hierarchy of it all.
App's root view is a UINavigationController which has class ViewController as visible for the first view seen. I have a UIButton which will fire a movie that loops until I tap it twice which will have the navigation controller push a new ViewController that I have made called PUPPETS1 onto the screen. This VC has its own xib. The xib has a UIImageView. What I want to have happen is to start playing a movie once I swipe up on the screen, but that never happens, and the console never shows my NSLog from the 2nd VC's method.
- (void)loopVideo {
NSURL *videoURL = [[NSBundle mainBundle] URLForResource:#"warpspeed" withExtension:#"mov"];
UIView *patternView = [[UIView alloc] initWithFrame:self.view.bounds];
patternView.backgroundColor = [UIColor blackColor];
[self.moviePlayer2.backgroundView addSubview:patternView];
self.moviePlayer2 = [[MPMoviePlayerController alloc] initWithContentURL:videoURL];
[self.moviePlayer2 setControlStyle:MPMovieControlStyleDefault];
self.moviePlayer2.controlStyle = MPMovieControlStyleNone;
self.moviePlayer2.scalingMode = MPMovieScalingModeAspectFill;
self.moviePlayer2.movieSourceType = MPMovieSourceTypeFile;
[self.moviePlayer2 setAllowsAirPlay:YES];
self.moviePlayer2.view.frame = self.view.frame;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(puppetOne)];
tapGesture.numberOfTapsRequired = 2;
tapGesture.numberOfTouchesRequired = 1;
UIView *aView = [[UIView alloc] initWithFrame:self.moviePlayer2.backgroundView.bounds];
[aView addGestureRecognizer:tapGesture];
[self.view.window addSubview:aView];
[self.view addSubview:self.moviePlayer2.view];
self.moviePlayer2.repeatMode = MPMovieRepeatModeOne;
[self.moviePlayer2 play];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
In the 2nd VC, the PUPPETS1 one:
- (void)viewDidLoad {
[super viewWillAppear:YES];
UISwipeGestureRecognizer * swipeRec = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(playPuppets)];
swipeRec.direction = UISwipeGestureRecognizerDirectionUp;
UIView *aView2 = [[UIView alloc] initWithFrame:self.view.bounds];
[aView2 addGestureRecognizer:swipeRec];
[self.view addSubview:aView2];
// Do any additional setup after loading the view from its nib.
}
-(void)playPuppets {
NSLog(#"PLAYING");
NSURL *videoURL = [[NSBundle mainBundle] URLForResource:#"SundayPuppets" withExtension:#"m4v"];
//filePath may be from the Bundle or from the Saved file Directory, it is just the path for the video
AVPlayer *player = [AVPlayer playerWithURL:videoURL];
AVPlayerViewController *playerViewController = [AVPlayerViewController new];
playerViewController.player = player;
//[playerViewController.player play];//Used to Play On start
[self presentViewController:playerViewController animated:YES completion:nil];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
The frame of the view to which you're attaching the gr depends on it's parent view's bounds, and those aren't initialized yet in viewDidLoad.
Move the setup to after layout is complete (and tweak it to run just once, on the first layout change), i.e.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
UIView *swipeView = [self.view viewWithTag:999];
// only do this if we haven't done it already
if (!swipeView) {
// now that self.view.bounds is initialized...
swipeView = [[UIView alloc] initWithFrame:self.view.bounds];
swipeView.tag = 999;
// the rest of your OP setup code is fine, and goes here
UISwipeGestureRecognizer * swipeRec = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(playPuppets)];
swipeRec.direction = UISwipeGestureRecognizerDirectionUp;
[swipeView addGestureRecognizer:swipeRec];
[self.view addSubview:swipeView];
NSLog(#"%#", swipeView);
}
}
EDIT The code above attached the gesture recognizing view correctly, providing a fix in the second view controller. It turns out that the other problem was the previous view controller was not properly removing the (deprecated) MPMoviePlayer, resulting in touches not functioning on the pushed vc. The entire reworked ViewController.m can be found in the chat linked below, but the fix for the touches issue was here...
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.moviePlayer stop];
[self.moviePlayer.view removeFromSuperview];
self.moviePlayer = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

Frame not being set with initWithFrame with xib

I am having huge problems with learning autolayout especially when integrating xibs and uiscrollviews. In an effort to simplify my problem, I started a new project with a single view tied to a storyboard. I then subclassed UIView (Diptic) and created a xib file for it. My storyboard is not using autolayout, but my Diptic xib is. Right now I want to have a horizontal scrollView with a few Diptic instances layed out across it. But I am only ever getting the first diptic instance to show up, because the frame isn't being initialized correctly.
In my ViewController.m's viewDidLoad:
self.scrollView.contentSize = CGSizeMake((self.view.frame.size.width+10)*5, self.view.frame.size.height);
for (int i = 0; i < 5; i++){
int x = i*(self.view.frame.size.width+10);
Diptic *diptic = [[Diptic alloc] initWithFrame:CGRectMake(x, 50, self.view.frame.size.width, self.view.frame.size.height-100)];
[self.scrollView addSubview:diptic];
[array addObject:diptic];
UIView *greenBox = [[UIView alloc] initWithFrame:CGRectMake(x, 5, 40, 40)];
greenBox.backgroundColor = [UIColor greenColor];
[self.scrollView addSubview:greenBox];
}
Diptic.m
#import "Diptic.h"
#implementation Diptic
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSArray* views = [[NSBundle mainBundle] loadNibNamed:#"Diptic" owner:nil options:nil];
self = [views objectAtIndex:0];
self.label.text = #"WOOT";
self.backgroundColor = [UIColor redColor];
}
return self;
}
#end
If I set the frame after I add it to the view it seems to work, but why can't I set the frame with initWithFrame?
The problem here is that you assign the frame to a view that is created with a call to [super initWithFrame:frame]. When the NIB is loaded in - (id)initWithFrame:(CGRect)frame, self is replaced with a view from [views objectAtIndex:0] and the frame for this view is never set. Instead, eliminate the call to super and load the view from the NIB instead:
#import "Diptic.h"
#implementation Diptic
- (id)initWithFrame:(CGRect)frame
{
NSArray *views = [[NSBundle mainBundle] loadNibNamed:#"Diptic" owner:nil options:nil];
self = [views objectAtIndex:0];
if (self) {
self.frame = frame;
self.label.text = #"WOOT";
self.backgroundColor = [UIColor redColor];
}
return self;
}
#end
When init a view/controller from storyboard or from xib, the init method is
- (id)initWithCoder:(NSCoder *)decoder
try to do other things in this method

UITapGestureRecognizer EXC_BAD_ACCESS in self-contained UIView subclass

I've used UITapGestureRecognizer tons of times, but in this case I'm getting EXC_BAD_ACCESS when a tap occurs. I think it has to do with the fact that I'm adding it to an Alert-type overlay view. And for some reason the overlay view isn't getting retained even though it's on-screen.
I'm creating the view like this:
HelperView *testHelper = [HelperView helperWithBodyText:#"test text"];
[testHelper presentAtPoint:screenCenter];
The convenience method in HelperView.m looks like this:
+ (id)helperWithBodyText:(NSString*)text
{
return [[self alloc] initWithFrame:CGRectZero bodyText:text];
}
And the rest of the code looks like this:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.container = [[AGWindowView alloc] initAndAddToKeyWindow];
self.container.supportedInterfaceOrientations = UIInterfaceOrientationMaskLandscapeRight;
self.overlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
self.overlay.backgroundColor = [UIColor redColor];
self.overlay.alpha = 0.6;
[self.container addSubviewAndFillBounds:self.overlay]; //this fills the screen with a transparent red color, for testing
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissMe:)];
[self.overlay addGestureRecognizer:tap]; //I want to dismiss the whole view if the overlay is tapped
self.content = [[UIView alloc] initWithFrame:CGRectZero];
}
return self;
}
- (id)initWithFrame:(CGRect)frame bodyText:(NSString*)bodyText
{
self = [self initWithFrame:frame];
if (self) {
//TEST frame
self.content.frame = CGRectMake(0, 0, 217, 134);
// Initialization code
UIImage *bgImage = [[UIImage imageNamed:#"helper-bg"]
resizableImageWithCapInsets:UIEdgeInsetsMake(30, 28, 20, 20)];
UIImageView *bgImgView = [[UIImageView alloc] initWithImage:bgImage];
bgImgView.bounds = self.content.frame;
[self.content addSubview:bgImgView];
}
return self;
}
- (void)presentAtPoint:(CGPoint)loc
{
CGPoint newPoint = [self.container convertPoint:loc toView:self.container];
self.content.center = newPoint;
[self.container addSubview:self.content];
}
- (void)dismissMe:(UITapGestureRecognizer*)recognizer
{
//this never happens - I get EXC_BAD_ACCESS when I tap the overlay
}
HelperView is not the view getting displayed and it's not getting retained, but you're using as the target for the gesture recognizer. The AGWindowView property "container" is getting displayed and retained by its superview. Your code needs refactoring since you have this view HelperView that doesn't ever display anything itself, but if you want it to work like this you need to retain HelperView so it doesn't automatically get released. You can do this by assigning it to a strong instance variable.

Show view from xib in ScrollView

firstly I want to say that I am newbie at iOS development. I have a problem with showing a view from xib file in ScrollView.
This MyView is only green background with one label. Here is a code from its controller:
#implementation MyView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
//init code
}
return self;
}
- (id) initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
[self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
}
return self;
}
#end
And here is a code from main controller where is the ScrollView:
- (void)viewDidLoad
{
[super viewDidLoad];
scroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 67, self.view.frame.size.width, self.view.frame.size.height)];
scroll.pagingEnabled = YES;
scroll.frame = CGRectMake(0, 20, 320, 350);
scroll.contentSize = CGSizeMake(320, 350);
scroll.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
scroll.bounces = YES;
scroll.bouncesZoom = YES;
scroll.scrollEnabled = YES;
scroll.minimumZoomScale = 0.5;
scroll.maximumZoomScale = 5.0;
scroll.delegate = self;
scroll.pagingEnabled = YES;
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
UIView *myView = [nibContents objectAtIndex:0];
myView.frame = CGRectMake(0,20,300,300);
[scroll addSubview:myView];
}
Only thing I want to do is that I want to see MyView (green background with one label) inside the ScrollView. If I run this app, scrollview is empty, nothing is shown inside.
Probably it is a stupid question but as I said, I am newbie at this. I have a PageControll app from Apple but for me this is quite complicated code in my iOS beginning. Of course I was googling... Please tell me where is a problem. Thank you
Where do you define scroll? Is it a property of your viewcontroller?
I think the problem here is that you don't add scroll view to your viewcontroller's view with
[self.view addSubview:scroll]
Everything seems to right. Just retain the view in your class that you are receiving from the following line and add that as a subview to your scrollview.
self.myView = [nibContents objectAtIndex:0];
Best Regards.

How to position a View as Subview?

I want to position a view (red box with label and button) prepared in IB as Subview on the screen programmatically. The positioning works fine with the "blue lines" (there might actually be a better way to simply draw a line on a view?!). But the view of the "AttributeApertureView" view object sticks to the top instead of following the initFrame Parameter starting at y=60 as you can see in the screenshot.
//Add blue Line
CGRect lineB = CGRectMake(0, 48, bounds.size.width, 0.5);
UIView *secondLine = [[UIView alloc]initWithFrame:lineB];
[secondLine setBackgroundColor:[UIColor colorWithRed:0.396 green:0.796 blue:0.894 alpha:1]];
[[self view]addSubview:secondLine];
//Add Attribute Aperture
AttributeApertureView *aperture = [[AttributeApertureView alloc]initWithFrame:CGRectMake(0, 60, bounds.size.width, 50)];
[aperture setBackgroundColor:[UIColor redColor]];
[[self view]addSubview:aperture];
Init function in AttributeApertureView class. This should basically only load the related nib file with the IB Interface.
#implementation AttributeApertureView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *nib = [[NSBundle mainBundle]loadNibNamed:#"AttributeApertureView"
owner:self
options:nil];
self = [nib objectAtIndex:0];
}
return self;
}
What is the best way to position the "aperture" view on the screen?
Seems like calling loadNibNamed: resets the view's frame to what it is in the nib. This should work:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *nib = [[NSBundle mainBundle]loadNibNamed:#"AttributeApertureView"
owner:self
options:nil];
self = [nib objectAtIndex:0];
self.frame = frame;
}
return self;
}
because self = [super initWithFrame:frame]; sets the frame once, but self = [nib objectAtIndex:0]; ends up changing it again because you are resetting the whole view, so you have to set the frame again to be the one in the argument.

Resources