Second UIWindow does not get any touch events - ios

I have a second window I create to show a custom notification in my app:
#property (strong, nonatomic) UIWindow *window2;
And that's how I make it appear:
self.window2 = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, SCREEN_BOUNDS.size.width, 50)];
self.window2.backgroundColor = [UIColor blackColor];
self.window2.windowLevel = UIWindowLevelAlert;
self.window2.rootViewController = custonViewController;
[self.window2 makeKeyAndVisible];
The problem is that no touch event inside custonViewController works.
I tried everything, even subclass UIWindow and override the (void)sendEvent:(UIEvent *)event. But it doesn't work either.
How can I make my appdeledate to know the window2 has been touched/tapped?
Any ideas?

Nevermind. I found out what happened.
I was animating the UIWindow to appear and disappear automatically, in a sequence of animations after the completion from the previous one with delay set with some seconds.
It locked any touch events during the whole sequence of animations.
So i separated the animations and put it in async threads, so it would receive touches normally.
bye!

Related

UITapGestureRecognizer cause view not deallocate

I ran into a weird bug :
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tap)];
TestView *testView = [[TestView alloc] initWithFrame:self.view.bounds];
[testView addGestureRecognizer:tap];
testView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:testView];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSLog(#"testView removeFromSuperview");
[testView removeFromSuperview];
});
}
-(void)tap{
NSLog(#"test");
}
TestView is a subclass of UIView that prints log when -(void)dealloc is called.
If I do nothing and wait till the testView removes from its superview,the console logs :
2016-08-25 15:08:30.176 TapTest[12786:238984] testView removeFromSuperview
2016-08-25 15:08:30.176 TapTest[12786:238984] test view dealloc
However , if i tap on the view that actually triggers the tap action,the console only logs :
2016-08-25 15:09:43.605 TapTest[12802:240217] test
2016-08-25 15:09:45.306 TapTest[12802:240217] testView removeFromSuperview
I've done quite some research , but still have no idea of what's going on.
I am using Xcode 7.3(7D175) and iOS9.
EDIT: A friend of mine says this has something to do with runloop and autorealease pool , and after testView is removed from superview , i tap on the view again to trigger the next runloop , and the text view is deallocated.
This is probably due to an implementation detail of the touch management system.
As you've mentioned in your edit, if you touch the view once more after your TestView has been removed, you will notice that it gets deallocated at this point.
If you set a breakpoint in dealloc, you will see that it gets deallocated when a UITouch gets deallocated.
At this point you can only guess what is happening, but most probable thing is that iOS keeps around the last touch that happened with a UITouch object. An a UITouch keeps a reference to the view in which the touch happened (see the view property of UITouch). In this case this is your TestView and this is why it doesn't get deallocated immediately.
I have figured something.
I added some tricks for catching retain count on arc.
NSLog(#"testView removeFromSuperview -> %d", (int)[testView performSelector:NSSelectorFromString(#"retainCount")]);
none tap:
testView removeFromSuperview -> 3
with tap:
testView removeFromSuperview -> 4
something has grabbed testview in tap gesture with click tap event once or more. I guess this happening by relative with some logic on main run loop event.
so funny question. thank you. I will digging more.

Why is animated UIButton's position reset when clicked?

Short description: When the keyboard is shown I translate a button up, but upon clicking the button it pops back down to its original position.
Long description: I have two buttons, one on top of the other. One is hidden until certain criteria are met, then it becomes visible and the other is hidden.
#property (weak, nonatomic) IBOutlet UIButton *skipButton;
#property (weak, nonatomic) IBOutlet UIButton *saveButton;
#property (assign, nonatomic) BOOL saveButtonEnabled;
#property (assign, readonly, nonatomic) CGRect fieldContainerDefaultFrame;
#property (assign, readonly, nonatomic) CGRect skipButtonDefaultFrame;
When view loads I cache the default positions
- (void)viewDidLoad
{
[super viewDidLoad];
...
_fieldContainerDefaultFrame = self.fieldContainerView.frame;
_skipButtonDefaultFrame = self.skipButton.frame;
[self.saveButton setHidden:YES];
self.saveButtonEnabled = NO;
}
The buttons are wired to outlets correctly and these are their "touch up inside" actions
#pragma mark - Actions
- (IBAction)didPressSaveButton:(id)sender
{
// Code here to persist data
// Code here to push segue to next scene
}
- (IBAction)didPressSkipButton:(id)sender
{
// Code here to push segue to next scene
}
Here are the methods that respond to the keyboard notifications. I do register for the notifications I'm just not including that code. (There is no issue there, all this stuff gets run correctly.)
NOTE: Both buttons are contained in the same "container" frame (along with other fields), which translates up. The buttons translate up an additional amount. The translation works in general, the only issue is the reseting behavior of the button upon click.
#pragma mark - Notification handlers
- (void)handleKeyboardWillShowNotification:(NSNotification *)note
{
if([self.bioTextView isFirstResponder])
{
CGRect newFieldContainerFrame = self.fieldContainerDefaultFrame;
CGRect newSkipButtonFrame = self.skipButtonDefaultFrame;
newFieldContainerFrame.origin.y += AGSTSignupDetailsFieldContainerEditingOffset;
newSkipButtonFrame.origin.y += AGSTSignupDetailsSkipButtonEditingOffset;
NSTimeInterval duration = [note.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve animationCurve = [note.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
[UIView animateWithDuration:duration animations:^{
[UIView setAnimationCurve:animationCurve];
self.fieldContainerView.frame = newFieldContainerFrame;
self.skipButton.frame = newSkipButtonFrame;
self.saveButton.frame = newSkipButtonFrame;
}];
}
}
- (void)handleKeyboardWillHideNotification:(NSNotification *)note
{
NSTimeInterval duration = [note.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve animationCurve = [note.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
[UIView animateWithDuration:duration animations:^{
[UIView setAnimationCurve:animationCurve];
self.fieldContainerView.frame = self.fieldContainerDefaultFrame;
self.skipButton.frame = self.skipButtonDefaultFrame;
self.saveButton.frame = self.skipButtonDefaultFrame;
}];
}
I'd appreciate any insights or suggestions as to how to fix the bug or how to craft a better solution. Thanks.
Okay, I fixed the problem. While I appreciate rob mayoff's response as an indication of better practices (i.e., don't set frames when using autolayout), that was not the actual cause of the problem in my case, thus I don't consider my Q&A to be a duplicate of anything I could find. I'm going to post this answer in case someone else runs across a similar problem.
It turns out, as I suspected earlier this morning, that the button's push segue did two mutually incompatible things: (1) it caused the keyboard to disappear, which caused the fields to be translated downward; and (2) it caused the whole scene to animate away as the new VC comes into place, which apparently cancelled all pending animations (i.e., the translation) and caused those translations to abruptly jump to their destination states.
The quickest solution was to set a BOOL didPressSaveButton to NO on viewDidLoad and only set it to YES once the button is pushed, and then check that BOOL before every relevant translation animation: if the BOOL is NO, go ahead and do the animation, otherwise do nothing. This will prevent the abrupt final animation before the push segue.
The better solution, and one I will implement soon, will be to replace my use of frames with autolayout constraints.

Memory Leak in using MPMoviePlayerController

In my project I manually create views without using storyboard. I have tried to play a Video when I tap an image. It works fine. But everytime when I check it shows memory leak when I tap the image, I have searched about this a lot and applied but no use.
In my Appdelegate.h file:
#property (strong, nonatomic) MPMoviePlayerController *theMoviePlayer;
#property (strong, nonatomic) UIImageView *image1;
In .m file:
-(void) startPage{
.....
_image1 = [[UIImageView alloc] initWithFrame:CGRectMake((self.window.frame.size.width/2)-25, 40, 50, 50)];
[_image1 setUserInteractionEnabled:YES];
_image1.image = [UIImage imageNamed:#"image_2.jpg"];
_tapImage1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(image1Tapped:)];
[_image1 addGestureRecognizer:_tapImage1];
.....}
In imageTapped(),
-(void) image1Tapped:(UITapGestureRecognizer *)sender
{
.....
[_image1 removeFromSuperview];
_theMoviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
[_theMoviePlayer setControlStyle:MPMovieControlStyleFullscreen];
[_theMoviePlayer.view setFrame:CGRectMake(0,-55, self.window.frame.size.width, self.window.frame.size.height)];
[_theMoviePlayer setScalingMode:MPMovieScalingModeAspectFill];
UIWindow *backgroundWindow = [[UIApplication sharedApplication] keyWindow];
[backgroundWindow addSubview:_theMoviePlayer.view];
[_theMoviePlayer.view bringSubviewToFront:backgroundWindow];
[_theMoviePlayer play];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(movieFinished:)
name:MPMoviePlayerPlaybackDidFinishNotification object:_theMoviePlayer];
...}
I get a memory leak everytime it enters imageTapped: method. Any help will be appreciated.
#property (strong, nonatomic) MPMoviePlayerController *theMoviePlayer;
#property (weak, nonatomic) UIImageView *image1;
another think is that your theMoviePlayer is not remove, try to make its view transparency for see if is already working behind the new one
I am trying to help you.
In -(void) startPage you allocate _image1 object
And you are removing object in method -(void) image1Tapped:(UITapGestureRecognizer*)sender
using [_image1 removeFromSuperview]; method
means now _image1 is nil and when -(void) image1Tapped:(UITapGestureRecognizer *)sender method called at that time when u fetch _image1 object at that time _image1 is already nil
so its give Memory Leak warning.
And solution for this one is:
1.**Show/Hide _image1 object** or
every time you need to do proper allocation and remove image1 object in this method -(void) image1Tapped:(UITapGestureRecognizer *)sender, based your requirement.
First try this solution and there will be Memory Leak Warning removed.
The Compiler check all over steps in advance so it recognize as Warning.
In Some cases if your logic is wrong then compiler inform us Wrong logic.
if u want to check that click on blue arrow of memory warning button it will give exaplanation of your logic or warning with assumption.
I found the problem, its with the device iPad(iOS version 5). It shows no leaks when I checked with iPad4 (iOS version 7).

UIDynamicAnimator refuses to reach equilibrium when a UIGravityBehavior is active

I'm working on code for an expandable tray view that uses UIDynamicAnimator to achieve a nice expand/contract animation.
To achieve a realistic acceleration I use UIGravityBehavior to make my tray fall, until the "tab" of the tray hits the bottom of the screen.
This works well, but even though all items in the scene have stopped moving, UIDynamicAnimatorDelegate dynamicAnimatorDidPause: is never called. This means that the animator continues using CPU cycles to animate the scene ( the delegate is set, and fires for UIDynamicAnimatorDelegate dynamicAnimatorDidPause: ).
I tried removing the UIGravityBehavior from the scene, which did indeed cause the animator to stop in the end. I can't time the removal of the gravity behavior right though, since I need to remove it from the scene once everything has stopped moving.
I understand that gravity is a constant force, but I still assumed it would stop the animator once everything has 0 velocity and 0 acceleration.
Is this last assumption false?
Anyone having similar problems?
You are correct that the animator should pause once everything comes to rest.
Check what items are attached to your gravity behavior, and make sure that there aren't other items still falling. For example, it is easy to accidentally create the following bug:
Add a view to gravity and collision
Remove view from superview and from collision
Fail to remove view from gravity
In this situation, the "ghost item" will fall forever.
Another possible problem (though less likely given your description) is if your items are attached to other behaviors that are causing infinite but small "bounce." I would check the full list of behaviors on your animator (remember to check child behaviors, too). In particular I'd be interested in any UIDynamicItemBehavior that adds elasticity.
EDIT:
You may also want to go the other way. Start with a very basic dynamics system and add components from yours until you can reproduce the problem. For instance, the following does converge quite quickly (logging "pause"):
#interface PTLViewController () <UIDynamicAnimatorDelegate>
#property (nonatomic, strong) UIDynamicAnimator *animator;
#end
#implementation PTLViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100,100,100,100)];
view.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:view];
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.animator.delegate = self;
UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[view]];
collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collisionBehavior];
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[view]];
[self.animator addBehavior:gravityBehavior];
}
- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator {
NSLog(#"pause");
}
#end
To your question about getting all item velocities, I don't know of an easy way to do that. Unfortunately, UIDynamicAnimator doesn't directly know all of its items. This is indirectly because UIDyanamicBehavior doesn't include an items property. If this bothers you as much as it does me, consider duping radar://15054405.
But there is a solution if you just want to know the current linear velocity of specific items. Just add a UIDynamicItemBehavior with a custom action to log it:
UIDynamicItemBehavior *dynamicItemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:#[view]];
__weak UIDynamicItemBehavior *weakBehavior = dynamicItemBehavior;
dynamicItemBehavior.action = ^{
NSLog(#"Velocity: %#", NSStringFromCGPoint([weakBehavior linearVelocityForItem:view]));
};
[self.animator addBehavior:dynamicItemBehavior];
I had a similar issue recently. In the end I used a UICollisionBehavior with boundaries instead of items (because otherwise the moving items were bumping the others...) and implement the delegate method collisionBehavior:beganContactForItem:withBoundaryIdentifier:atPoint: to know when I should remove the gravity
UICollisionBehavior *collide = [[[UICollisionBehavior alloc] initWithItems:borders] retain];
[collide addItem:movingItem];
[collide setCollisionMode:UICollisionBehaviorModeBoundaries];
[collide setTranslatesReferenceBoundsIntoBoundary:YES];
If you find a better solution, let me know :) !
My problem is the same. My animator never comes to rest so once started, my app consumes 3 to 4% CPU forever. My views all appear to stop moving within 1/2 second. So rather than figure out why I'm not reaching equilibrium, I just hit it with a hammer and kill the animator with a timer. I give it 2 seconds.
- (void)createAnimator {
if (_timer) {
[_timer invalidate];
}
if (_animator) {
[_animator removeAllBehaviors];
}
_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
// create all behaviors
// kill the animator in 2 seconds
_timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(killAnimator:) userInfo:nil repeats:NO];
}
- (void)killAnimator:(NSTimer *)timer {
[_animator removeAllBehaviors];
_animator = nil;
_timer = nil;
}

If custom UIView has >1 sublayers, app crashes on touchesBegan, why?

I have a few custom UIView objects that all handle drawing like this:
- (void)drawRect:(CGRect)rect {
// ^ I init the layers 1 and 2
[self.layer insertSublayer:layer1 atIndex:0]; // 1 or more
[self.layer insertSublayer:layer2 atIndex:1];
}
They also have a - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; with nothing else inside but a NSLog.
I add them all inside my main ViewController like so:
- (void)viewDidLoad
{
[super viewDidLoad];
CustomView *myViewWith1Layer = [[CustomView alloc] initWithFrame:CGRectMake(20, 20, 440, 260)];
[self.view addSubview:myViewWith1Layer];
CustomViewLayered *myViewWith2Layer = [[CustomViewLayered alloc] initWithFrame:CGRectMake(40, 260, 200, -120)];
[self.view addSubview:myViewWith2Layers];
}
When I run my app, if I tap on a view that has only a single layer - I get my NSLog to show, and everything's fine. If, on the other hand, I tap on views with 1+ layers, the app crashes (objc_msgSend log shows up with "EXC_BAD_ACCES (code=1, address=..."). I guess this is somehow related with ARC, which I have enabled.
How do I add multiple layers to a view, without it being messed up by ARC?
I don't think that this is an ARC problem, but creating and inserting the layers in drawRect
is wrong. This should be done (only once) in the init method of the view, e.g. in initWithFrame.
In my case, the solution was definitely related to ARC.
When initialising my delegates, I immediately assigned them to the layer.delegate property, and ARC would remove the object from existence immediately after that.
So for each layer, I add a strong #property (strong, nonatomic) delegatesClass *delegatesName and initialise straight to the property. After that, I assign the layer.delegate = self.delegatesName.
This did solve the issue, although I'm not sure if it is the right way to do things.

Resources