I have a subview with live view from the back camera. I am animating it when toggling the camera to front. Because I could't sync the animation and the toggle, I covered it with another view with plain black background. Because toggle is slower, I want to remove the black subview after toggle was performed. But when I do it, it won't disappear. Debugger shows all NSLogs I put in, and the debugger even stops when I put breakpoints anywhere, so the code is reachable. But the UIView is simply not modified and still holds the black subview.
Interesting fact: when I turn display off with the top button and then back on again, the black view is gone!
CODE:
- (IBAction)cameraChanged:(id)sender {
NSLog(#"Camera changed");
UIView *animView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.videoPreviewView.frame.size.width, self.videoPreviewView.frame.size.height)];
animView.tag = 42;
[animView setBackgroundColor:[UIColor blackColor]];
[UIView transitionWithView:self.videoPreviewView duration:1.0
options:self.animate ? UIViewAnimationOptionTransitionFlipFromLeft:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[self.videoPreviewView addSubview:animView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul), ^{
[self.captureManager toggleCamera];
NSLog(#"Done toggling");
[self done];
});
}
completion:^(BOOL finished){
if (finished) {
//
}
}];
self.animate = !self.animate;
}
- (void)done {
for (UIView *subview in [self.videoPreviewView subviews]) {
NSLog(#"In for");
if (subview.tag == 42) {
NSLog(#"removing");
[subview removeFromSuperview];
}
}
}
Anybody knows why is this happening and how to solve it?
UIView Update should be run on Main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul), ^{
//BlahBlah
dispatch_sync(dispatch_get_main_queue(), ^{
//update your UIView
});
});
Related
My code like this:
- (void)setupSubViews {
[self addSubview:self.textView];
[self addSubview:self.sendButton];
[self.textView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(5);
make.bottom.equalTo(-5);
make.leading.equalTo(9);
}];
[self.sendButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self);
make.trailing.equalTo(-9);
make.leading.equalTo(self.textView.mas_trailing).offset(7);
make.width.equalTo(60);
make.height.equalTo(40);
}];
}
The show function to make the textView becomeFirstResponder
- (void)show {
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
[keyWindow addSubview:self];
[self mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.equalTo(0);
make.bottom.equalTo(InputHeight);
}];
[self.textView becomeFirstResponder];
}
This view add to the keyWindow at the bottom of mainScreen, it's translate will changing follow the keyboard when the keyboard's frame changed.
- (void)keyboardWillChangeFrame:(NSNotification *)noti {
if (!self.textView.isFirstResponder) {
return;
}
NSValue *endFrame = noti.userInfo[#"UIKeyboardFrameEndUserInfoKey"];
CGFloat endY = [endFrame CGRectValue].origin.y;
BOOL isBackToBottom = endY == UI_SCREEN_HEIGHT;
CGFloat needOffset = endY - (UI_SCREEN_HEIGHT + InputHeight);
if (isBackToBottom) {
[UIView animateWithDuration:0.25 animations:^{
self.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
} else {
[UIView animateWithDuration:0.25 animations:^{
self.transform = CGAffineTransformMakeTranslation(0, needOffset);
}];
}
}
Use Case:
viewController has a button, button click action like this:
- (void)beginChat {
[self.inputView show];
}
self.inputView is the above customView,
- (LiveChatInputView *)inputView {
if (!_inputView) {
_inputView = [LiveChatInputView new];
_inputView.sendBlock = ^(NSString *string) {
....
};
}
return _inputView;
}
Now my question is that when we call show function at the first time after application launched, the keyboard will not show, but call show function in second time, every thing is fine.
How to deal with this question, THX.
Calling this method is not a guarantee that the object will become the first responder.
As per Apple,
A responder object only becomes the first responder if the current
responder can resign first-responder status (canResignFirstResponder)
and the new responder can become first responder.
Try calling becomeFirstResponder like below,
[self.textView performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0];
I made a mistake, call [self removeFromSuperview] in the function of keyboardWillChangeFrame:.
I thought UIKeyboardWillChangeFrameNotification could only post it once at the keyboard show or hide, so i put the implementation of back action in keyboardWillChangeFrame:, and controlled by endFrame.origin.y == UI_SCREEN_HEIGHT
In practice, the fact is that:
when the keyboard show, NSNotificationCenter will post UIKeyboardWillChangeFrameNotification three times:
first
second
third
Tips:
only first time show the keyboard after application launched, the first UIKeyboardWillChangeFrameNotification of above three, the endFrame.origin.y is equal to UI_SCREEN_HEIGHT
My solution:
Observer the UIKeyboardWillHideNotification, and do back action in this callback.
Thanks for all answers of this question.
I have a UIScrollView that has a bunch of items in it, similar to a facebook feed. In that feed I have a bunch of images that are being loaded from the internet. When the images are loaded they fade in. The problem is that if I am scrolling the scroll view, then the image animation to fade in doesn't occur until my finger is lifted off the scrollview. Is there any way to update the subviews of the UIScrollView view while scrolling?
-(void)loadStuff:(DipticView*)diptic{
//DipticView is a UIView subclass that holds the imageview and some other information
ConnectionManager *conman = [[ConnectionManager alloc] init];
WebImage *webimage = [diptic.item.images firstObject]; //webimage holds the imageurl and the image
[conman getImageWithURL:webimage.image_URL inBackgroundWithBlock:^(NSString *error, id image) {
webimage.image = image;
diptic.imageView.image = image;
diptic.imageView.alpha = 0;
diptic.userInteractionEnabled = NO;
[UIView animateWithDuration:.4 animations:^{
diptic.imageView.alpha = 1;
} completion:^(BOOL finished) {
diptic.userInteractionEnabled = YES;
}];
//The diptics are all subviews of the scrollview.
}];
}
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:.4 animations:^{
diptic.imageView.alpha = 1;
} completion:^(BOOL finished) {
diptic.userInteractionEnabled = YES;
}];
});
But I'm not sure that this method can work well with animateWithDuration.
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
Hifellows, please consider following scenario:
I have 3 methods, where on each create and add some objects like imageviews,activityindicators,etc.
So imagine:
- (void)anim1 {
// do some verifications like internet reachability. So create imageview1, imageview2, imageview3, activityindicator then add to subView.
[UIView animateWithDuration:1 delay:0.3 options:0 animations:^{
// Animate the alpha value of your imageView from 1.0 to 0.0 here
//...
} completion:^(BOOL finished) {
if(![(AppDelegate*)[[UIApplication sharedApplication] delegate] hasConnectivity])
{
NSLog(#"No Internet...");
return;
}
else {
// If i got internet connection, i create another imageview (checked image) and add to subview. So play with alpha animations then call the second method.
[UIView animateWithDuration:0.8 delay:0.8 options:0 animations:^{
someobject.alpha = 0.0f;
[someobject setAlpha:0.5f];
[someobject setAlpha:0.5f];
} completion:^(BOOL finished) {
[self performSelector:#selector(anim2) withObject:nil afterDelay:0.1];
NSLog(#"Got Internet...");
}];
return;
}
}];
}
So... anim2:
- (void)anim2 {
// do some URL connections (POST FORM) with blocks using AFNetworking framework. So, depending on results of URL connection, i work with imageview2, add an activityindicator (while URL processing) then add to subView. To resume, let consider this:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.somepage.com"]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Response OK..");
// Here i create another imageview (checked image) and add to subview. So play with alpha animations then call the third method.
[UIView animateWithDuration:0.8 delay:0.8 options:0 animations:^{
someobject.alpha = 0.0f;
[someobject setAlpha:0.5f];
[someobject setAlpha:0.5f];
} completion:^(BOOL finished) {
[self performSelector:#selector(anim3) withObject:nil afterDelay:0.1];
}];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Response error. Aborting.");
}
}];
[operation start];
}
Finally, anim3 method:
- (void)anim3 {
// Work with imageview3, another activityindicator, then after animation delay, i create a new imageview (checked image). Then enable a uibutton.
...
}
So, when i start anim1, it do the verifications, then follow 'sequence', calling anim2... And so on... Anim1 = OK -> Anim2 = OK -> Anim3 -> Button enabled -> Clicked -> Set all imageviews to alpha = 0.70f.
All works as well, but i have create an another method to 'clean' these views, then start again.
Basically, it cancels all selectors, then removesfromSuperView all imageviews that have been created. And, call anim1 again.
Please consider:
-(void)cleanAnimViews
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(anim1) object:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(anim2) object:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(anim3) object:nil];
NSArray* subviews = [[NSArray alloc] initWithArray: scrollview.subviews];
for (UIView* view in subviews) {
if ([view isKindOfClass:[UIActivityIndicatorView class]]) {
[view removeFromSuperview];
}
}
for (UIView* view in subviews) {
if ([view isKindOfClass:[UIImageView class]] && view.tag == 200) {
[view removeFromSuperview];
}
if ([view isKindOfClass:[UIImageView class]] && view.tag == 201) {
[view removeFromSuperview];
}
if ([view isKindOfClass:[UIImageView class]] && view.tag == 202) {
[view removeFromSuperview];
}
if ([view isKindOfClass:[UIImageView class]] && view.tag == 203) {
[view removeFromSuperview];
}
if ([view isKindOfClass:[UIActivityIndicatorView class]] && view.tag == 204) {
[view removeFromSuperview];
}
if ([view isKindOfClass:[UIImageView class]] && view.tag == 250) {
[view removeFromSuperview];
}
}
[self performSelector:#selector(anim1) withObject:nil afterDelay:0.8];
}
I call to 'cleanAnimViews' on viewWillAppear, and using a RefreshControl object.
My problem:
When all tasks (think anim1, anim2 and anim3) already done, THEN i call 'cleanAnimViews' using pull to refresh, it beautifully removes all imageviews then start again from first method (anim1).
But, if i call 'cleanAnimViews' using pull to refresh during animX proccessing (between anim1 to anim2, or anim2 to anim3), i got mess because the 'cleanAnimViews' removes all imageviews, but the 'anim1 -> anim2 -> anim3' continues being processing at same time... So, i got two (or three) activityindicator (eg: from anim1 and anim3), mess with objects controls, because it executing an older 'proccess' on anim2 (eg) and new one to anim1 in sametime...
Let me explain better:
viewDidAppear ->
started anim1:
... This created the 3 images... Create ActivityIndicator... Verification has done... Create a "checked" imageview... So it perform selector anim2.
started anim2...
... This works with objects controls, Create ActivityIndicator, Verification has done... Create a "checked" imageview...
----> NOW i call "cleanAnimViews" from pull to refresh. But the anim2 already performed selector anim3...
So, it removes all imageview and at end of method start anim1 again (from cleanAnimViews), and as anim3 method already has started, it create imageviews, activityindicator and checked image again.. AND anim1 is doing ALL the process again on same time..
There is a way to 'cancel/stop' these animX method when i call 'cleanAnimViews'?
I ask this because when the view disappear and appear again all works beautifully. It stops all animX, and when appear again call anim1 again...
I have tried creating a BOOL flag on 'cleanAnimViews', then play with it on animX methods.. but dont works..
Sorry for long post, typos, and if i didn't so much clear... But i got no more idea.
Thanks in advance.
Yes there is a way to cancel all animation on your someObject so you have to call [someObject.layer removeAllAnimations];.
PS Don't forget to add QuartzCore so that you can access the layer.
I wrote a custom UITableViewCell - image view, button and three labels now I am trying to add some animations to it. So once I tap the button, it fades away and the spinner replaces the button. After two seconds the cell is overlaid with a red color, as a subview of cell fades in, then the indicator is removed and and red overlay starts fading back out. As well the button I previously removed fades back in.
(I could not phrase it a better way :P )
The method is :
-(void)rentButtonPressed:(id)sender
{
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[indicator startAnimating];
indicator.center = self.rentButton.center;
[UIView animateWithDuration:0.2
animations:^{self.rentButton.alpha = 0.0;}
completion:^(BOOL finished){
[self.rentButton removeFromSuperview];
[self addSubview:indicator];
}
];
UIView *overlay = [[UIView alloc] initWithFrame:self.backgroundImage.frame];
overlay.alpha = 0.0;
overlay.backgroundColor = [UIColor redColor];
[self.contentView addSubview:overlay];
[UIView animateWithDuration:0.4
delay:2.0
options:UIViewAnimationCurveEaseInOut
animations:^{
[indicator removeFromSuperview];
overlay.alpha = 0.4;
}
completion:^(BOOL finished){
[UIView animateWithDuration:0.4
animations:^{ overlay.alpha = 0.0; }
completion:^(BOOL finished)
{
[overlay removeFromSuperview];
}
];
[self.contentView addSubview:self.rentButton];
[UIView animateWithDuration:0.4 animations:^{ self.rentButton.alpha = 1.0;}];
[self.delegate didTryToRentMovieAtCell:self];
}
];
}
So the code does fade out the button, replace it with spinner and fades in the red overlay. The problem is, the red overlay does not fade away, but disappears same with the button, instead of fading in, it just appears.
During your animation, you are changing the view hierarchy by adding and removing subviews. The UIView class method animateWithDuration:animations:completion is intended only animating property changes in a view, and not for changing the view hierarchy.
Try using the UIView class method transitionWithView:duration:options:animations:completion: instead, and use the cell's content view as the "container."
This documentation is helpful in distinguishing between animating view property changes and animating view transitions, specifically the section "Changing the Subviews of a View":
http://developer.apple.com/library/ios/#documentation/windowsviews/conceptual/viewpg_iphoneos/animatingviews/animatingviews.html