I have made a game of Tic-Tac-Toe. If the game ends in a tie, an animated hudView appears for a couple of seconds. Then the game starts over, and that's when my problems occur. It doesn't respond to me tapping on screen to draw 'X' or 'O''s anymore. My suspicion is that the hudView is still there. I have tried different things to remove it, with no luck.
+ (instancetype)hudInView:(UIView *)view animated:(BOOL)animated
{
HudView *hudView = [[HudView alloc] initWithFrame:view.bounds];
hudView.opaque = NO;
[view addSubview:hudView];
view.userInteractionEnabled = NO;
[hudView showAnimated:YES];
return hudView;
}
And the animation:
- (void)showAnimated:(BOOL)animated
{
if (animated) {
self.alpha = 0.0f;
self.transform = CGAffineTransformMakeScale(1.3f, 1.3f);
[UIView animateWithDuration:4.5 animations:^{
self.alpha = 1.0f;
self.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
self.alpha = 0.0f;
}];
}
}
In the completion block, I have tried the following:
[self.superview removeFromSuperview];
In my ViewController where all of this gets called I've tried:
HudView *hudView = [HudView hudInView:self.view animated:YES];
hudView.text = #"\tIt's a Tie\n";
[hudView removeFromSuperview];
[self.view removeFromSuperview]
[hudView.superview removeFromSuperview];
Nothing I've tried so far is working. Any help would be much appreciated.
Related
I created a small custom UIView with a UILabel and a UIButton, this custom view is a banner to display at the top of the current view controller.
I load the view layout from a nib file and use a method from the custom view to display it with an animation, and the view will hide after a specific amount of time. Like this.
- (void)displayBannerInViewController:(UIViewController *)vc
{
CGFloat originY = 0;
if (vc.navigationController != nil) {
originY += 20 + vc.navigationController.navigationBar.bounds.size.height - self.bounds.size.height;
}
self.frame = CGRectMake(0,
originY,
[UIScreen mainScreen].bounds.size.width,
self.bounds.size.height);
if (vc.navigationController != nil) {
[vc.navigationController.view insertSubview:self atIndex:1];
} else {
[vc.view.window insertSubview:self atIndex:1];
}
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, self.bounds.size.height);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 delay:self.duration options:0 animations:^{
self.frame = CGRectOffset(self.frame, 0, -self.bounds.size.height);
} completion:^(BOOL finished) {
if (finished) {
[self removeFromSuperview];
}
}];
}];
}
I set the action for the button inside the banner with this
[self.actionButton addTarget:self
action:#selector(executeActionBlock)
forControlEvents:UIControlEventTouchUpInside];
After animation showing the banner, and before is hidden, no matter how many times I tap on the button, the executeActionBlock method is never called.
I made a test setting the initial frame of the banner to origin (0, 0) and without animation and then the button worked fine. So, I don't know if a problem of the animation or because the original frame of the banner is in a non visible position. BTW, is important for the banner to not be visible because on the app is showing from under the navigation bar.
Thanks
You've written your code so that you run a series of animations, where the view is on-screen during the delay of the last animation. By default user interaction is disabled on views while they are being animated. Try passing UIViewAnimationOptionAllowUserInteraction in options for all your nested animations.
https://developer.apple.com/reference/uikit/uiviewanimationoptions/uiviewanimationoptionallowuserinteraction?language=objc
Well, it seems I should keep looking more because I found this question with the same problem. I tried the solution there and now is working.
The problem is that linking animations is not a good idea for user interaction so I used a NSTimer instead, like this.
- (void)displayBannerInViewController:(UIViewController *)vc
{
CGFloat originY = 0;
if (vc.navigationController != nil) {
originY += 20 + vc.navigationController.navigationBar.bounds.size.height - self.bounds.size.height;
}
self.frame = CGRectMake(0,
originY,
[UIScreen mainScreen].bounds.size.width,
self.bounds.size.height);
if (vc.navigationController != nil) {
[vc.navigationController.view insertSubview:self atIndex:1];
} else {
[vc.view.window insertSubview:self atIndex:1];
}
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, self.bounds.size.height);
} completion:^(BOOL finished) {
[NSTimer scheduledTimerWithTimeInterval:self.duration target:self selector:#selector(hideBanner) userInfo:nil repeats:NO];
}];
}
- (void)hideBanner
{
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, -self.bounds.size.height);
} completion:^(BOOL finished) {
if (finished) {
[self removeFromSuperview];
}
}];
}
And now works like a charm.
I made an animation, fade in and fade out on a cell. When I press the cell(a button on the entire cell) the action is delegate via a protocol on a collectionView and pops to another controller (detailController).
The cell
- (IBAction)cellButtonPressed:(id)sender {
[self fadeIn];
}
-(void)fadeIn {
[UIView animateWithDuration:0.5
delay:0.0
options:0.0
animations:^{
self.coverAlbumPhoto.alpha = 0.0f;
self.shadowView.alpha = 0.0f;
self.mountainBorderImageView.alpha = 0.0f;
} completion:^(BOOL finished) {
[self fadeOut];
}];
}
-(void)fadeOut {
[UIView animateWithDuration:0.5
delay:0.0
options:0.0
animations:^{
self.coverAlbumPhoto.alpha = 1.0f;
self.shadowView.alpha = 1.0f;
self.mountainBorderImageView.alpha = 1.0f;
} completion:^(BOOL finished) {
if ([self.delegate respondsToSelector:#selector(tapCellButtonAtIndexPath:)]) {
[self.delegate tapCellButtonAtIndexPath:self.indexPath];
}
}];
}
Collection View
(void)tapCellButtonAtIndexPath:(NSIndexPath *)indexPath {
ArtworkModel *artworkModel = (ArtworkModel *)[listOfArtworks objectAtIndex:indexPath.row];
FBWorkDetailsViewController *dvc = [[FBWorkDetailsViewController alloc] initWithArtwork:artworkModel];
FBLeftMenuViewController *left = [[FBLeftMenuViewController alloc] init];
MFSideMenuContainerViewController *container = [MFSideMenuContainerViewController
containerWithCenterViewController: dvc
leftMenuViewController: left
rightMenuViewController:nil
withHeader: YES];
[container.titleLabel setText:#"WORK DETAILS"];
[self.navigationController pushViewController: container animated: YES];
}
The problem is that the animation is TOO SLOW. Can anybody explain me why? Thanks.
You have 2 animations, each with a duration of .5 seconds. That gives a total duration of 1 second. If you want it to be faster, use shorter durations. A total animation time of .2 to .3 seconds is probably a good place to start, so try backing down the duration of each step to 0.15 seconds (0.3 seconds total).
I am now confused a lot .
This condition is normal when the value yes animation action .
But state when no value completion called immediately.
I do not know if there is any difference between the two .
- (void)setDimView : (UIView*)targetView state:(BOOL)state
{
CGRect screenRect = [[UIScreen mainScreen] bounds];
UIView *dim = [[UIView alloc]initWithFrame:screenRect];
dim.tag = TAG;
if (state)
{
dim.alpha = 0.0;
}
[UIView transitionWithView:dim duration:0.5 options:UIViewAnimationOptionCurveEaseOut animations:^ {
if (state)
{
[targetView setHidden:NO];
[targetView addSubview:dim];
dim.backgroundColor = [UIColor blackColor];
dim.alpha = 0.6;
}
else
{
dim.alpha = 0.0;
}
}completion:^(BOOL finished) {
if (!state)
{
for (UIView *subview in [targetView subviews])
{
if (subview.tag == TAG)
{
[subview removeFromSuperview];
[targetView setHidden:YES];
}
}
}
}];
}
This block working sequence is first it called animation block and then completion block, but if you are set animation NO then its immediately call to completion.
I am not getting why you are so confused about it?
Difference between animation block and completion block
The code inside the animation block called as a loop. if a view's frame change is set in animation block it will execute as animation it take a duration to execute your code which you can configure. but if you are setting this view's frame change in completion block this will execute in single stretch after the completion of animation.
There's an array with uiviews. Their frames are set due to current interfaceOrientation
Then we add these views to self.view programmatically using
-(void)placeView
{
if ([self.arrayWithViews count] > 0)
{
[UIView animateWithDuration:1.f animations:^{
UIView *currentView = [self.arrayWithViews lastObject];
[currentView setAlpha:0.f];
[self.view addSubview:currentView];
[currentView setAlpha:1.f];
[self.arrayWithViews removeLastObject];
} completion:^(BOOL finished) {
[self placeView];
}];
}
}
Now, if the orientation change happens the views keep adding (with coordinates corresponding to previous orientation).
The question is - how to stop the animation process or how to prevent interface orientation change while the animation takes place.
P.S. [self.view.layer removeAllAnimations] doesn't work (I didn't figure out why)
Could you please advise what to do?
removeAllAnimations will still call completion block, so you repeat to call placeView, so you need add a flag:
-(void)placeView
{
if(!self.runAni){ //Add a flag to stop repeat to call [self placeView]
return;
}
if ([self.arrayWithViews count] > 0)
{
[UIView animateWithDuration:1.f animations:^{
UIView *currentView = [self.arrayWithViews lastObject];
[currentView setAlpha:0.f];
[self.view addSubview:currentView];
[currentView setAlpha:1.f];
[self.arrayWithViews removeLastObject];
} completion:^(BOOL finished) {
[self placeView];
}];
}
}
to stop animation, you should do :
self.runAni = NO;
[self.view.layer removeAllAnimations];
begin to do:
self.runAni = YES;
[self placeView];
Im looking for a way to copy the "blink" animation, that is played when pressing home+lock.
Does anyone know if this animation is available somehow?
On an iOS device you take a screenshot when you press home + lock and the screen flashes white. Do you mean this effect? If so, try this:
Add a UIView with a white background color to your view hierarchy such that it covers the whole screen. Then, start an animation that fades the opacity of this view to zero. On completion, remove the view from its superview:
[UIView animateWithDuration: 0.5
animations: ^{
whiteView.alpha = 0.0;
}
completion: ^(BOOL finished) {
[whiteView removeFromSuperview];
}
];
Try:
[UIView animateWithDuration:1 animations:^{
self.view.backgroundColor = [UIColor blackColor];
for (UIView *view2 in self.view.subviews) {
view2.backgroundColor = [UIColor blackColor];
}
}];
[UIView animateWithDuration:1 animations:^{
self.view.backgroundColor = [UIColor whiteColor];
for (UIView *view2 in self.view.subviews) {
view2.backgroundColor = [UIColor whiteColor];
}
}];
I have found very easy way to imitate exactly the Screenshot Flash Apple uses. I wish everyone try it at least once.
UIView * flashView = [[UIView alloc] initWithFrame:self.view.frame];
flashView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:flashView];
[UIView animateWithDuration:1 delay:0.3 options:0 animations:^{
flashView.alpha = 0;
} completion:^(BOOL finished)
{
[flashView removeFromSuperview];
}];
}
These examples gave me inspiration, but did not give me the desired effect. This is my attempt, looks pretty close to the real deal.
func mimicScreenShotFlash() {
let aView = UIView(frame: self.view.frame)
aView.backgroundColor = UIColor.whiteColor()
self.view.addSubview(aView)
UIView.animateWithDuration(1.3, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
aView.alpha = 0.0
}, completion: { (done) -> Void in
aView.removeFromSuperview()
})
}
Two problems with the current solutions:
putting views over other views can (in my experience with UICollectionView and other crap) trigger autolayout. Autolayout is bad. Autolayout makes the machine do a burst of work and can have side effects other than just calculating for no reason other than to burn CPU and battery. So, you don't want to give it reasons to autolayout, for instance, adding a subview to a view that really, really cares about keeping itself laid out nicely.
your view doesn't cover the whole screen necessarily, so if you want to flash the whole screen, you're better off using a UIWindow ... this should insulate from any view controllers that are touchy about having subviews added
This is my implementation, a category on UIView. I've included methods to take a screenshot before flashing the window, and saving it to the camera roll afterwards. Note that UIWindow seems to want to do its own animations when being added, it will normally fade in over maybe a third of a second. There may be a better way of telling it not to do this.
// stupid blocks
typedef void (^QCompletion)(BOOL complete);
#interface UIView (QViewAnimation)
+ (UIImage *)screenshot; // whole screen
+ (void)flashScreen:(QCompletion)complete;
- (UIImage *)snapshot; // this view only
- (void)takeScreenshotAndFlashScreen;
#end
#implementation UIView (QViewAnimation)
+ (UIImage *)screenshot;
{
NSArray *windows = [[UIApplication sharedApplication] windows];
UIWindow *window = nil;
if (windows.count) {
window = windows[0];
return [window snapshot];
} else {
NSLog(#"Screenshot failed.");
return nil;
}
}
- (UIImage *)snapshot;
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
+ (void)flashScreen:(QCompletion)complete;
{
UIScreen *screen = [UIScreen mainScreen];
CGRect bounds = screen.bounds;
UIWindow * flash = [[UIWindow alloc] initWithFrame:bounds];
flash.alpha = 1;
flash.backgroundColor = [UIColor whiteColor];
[UIView setAnimationsEnabled:NO];
[flash makeKeyAndVisible];
[UIView setAnimationsEnabled:YES];
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationCurveEaseOut
animations:^{
flash.alpha = 0;
}
completion: ^(BOOL finished)
{
flash.hidden = YES;
[flash autorelease];
if (complete) {
complete(YES);
}
}];
}
- (void)takeScreenshotAndFlashScreen;
{
UIImage *image = [UIView screenshot];
[UIView flashScreen:^(BOOL complete){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0),
^{
UIImageWriteToSavedPhotosAlbum(image,self,#selector(imageDidFinishSaving:withError:context:),nil);
});
}];
}
- (void)imageDidFinishSaving:(UIImage *)image
withError:(NSError *)error
context:(void *)context;
{
dispatch_async(dispatch_get_main_queue(),^{
// send an alert that the image saved
});
}
#end