I've have a bunch of .png files that i am supposed to animate over a period of 1.05 seconds. After the animation executes, i would like to have the last .png file show up permanently. I am using an UIIMageview IBOutlet to hold the animation. However, after the animation executes, the image just disappears and i do not see anything in on the screen.
The code for the animation is as follows:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self loadArrayForTickAnimation];
});
if (tickArray) {
self.animationView.animationImages = tickArray;
self.animationView.animationDuration = 1.05;
self.animationView.animationRepeatCount = 1;
[self.animationView startAnimating];
[self performSelector:#selector(showFinalImage) withObject:nil afterDelay:1.2];
}
}
- (void)showFinalImage
{
[self.animationView setImage:[UIImage imageNamed:#"check_37.png"]];
}
Here, check_1.png to check_37.png are the files that needed to be animated. I store them in an array called "tickArray", and populate the array using the [self loadArrayForTickAnimation] message.And finally, i have to set the UIIMage for the animationView as check_37.png
Fixed it !!! You need to set the image for the imageview before the animation starts. SO, i would have
self.animationView.image = [UIImage imageNamed:#"check_37.png"];
if (tickArray) {
self.animationView.animationImages = tickArray;
self.animationView.animationDuration = 1.05;
self.animationView.animationRepeatCount = 1;
[self.animationView startAnimating];
}
that finally made it work !!!
Related
I have a a XCUITest that fails to find a specific image, yet it is perfectly visible on the screen.(emulators and physical devices)
How can I workaround or make the the UIImage accessible to the tests?
All I want to validate is whether the element is visible and touchable on the screen.
The UIImage is added as a subview to a UITableView - it is placed at the bottom of the TableView.
- (void)viewDidLoad {
[super viewDidLoad];
// portions of code left out here
_aView = [[UIImageView alloc] initWithFrame:CGRectZero];
[[self aView] setContentMode:UIViewContentModeScaleAspectFit];
[[self aView] setImage:[UIImage imageNamed:IMG_NAME]] ;
[[self tableView] addSubview:[self aView]];
The UIImageView is later layed out:
- (void) viewWillLayoutSubviews {
// portions of code left out here
[[self aView] setFrame:CGRectMake(0, MAX([self tableView].contentSize.height - 41 ,[self tableView].bounds.size.height - 41) , 180, 30)];
[[self aView] setCenter:CGPointMake([self tableView].center.x, [self aView].center.y)];
Then when running the test:
let app = XCUIApplication()
//this works
let tapString = localizedString("skip")
let skipButton = app.buttons[tapString].firstMatch
if (skipButton.exists) {
skipButton.tap()
}
let theImage = app.images["img.png"]
let doesExist = theImage.exists //<< it never exists
Also doing a debug and printing all the images
does not show the image. I set a breakpoint at the line with doesExists
and in the debug window I run the command:
po app.images
Many images are found but not the particular image under test.
Is there perhaps an alternative to solve this problem?
(I already read this page, but it didn't help me CALayer not displaying)
I have a class called Image that has this field data:
uint8_t *data;
I already use this data to display this Image on a CALayer that I got gathering code from the internet.
I saw how to create another windows in my application, and I put a NSView inside it to display an Image using the method, I intend to display the histogram latter, but now I'm just trying to display again the same image:
-(void)wrapImageToCALayer: (CALayer*) layer{
if(!layer) return;
CGColorSpaceRef grayColorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef context = CGBitmapContextCreate(data, width, height, 8, step, grayColorSpace, kCGImageAlphaNone);
CGImageRef dstImage = CGBitmapContextCreateImage(context);
dispatch_sync(dispatch_get_main_queue(), ^{
layer.contents = (__bridge id)dstImage;
});
CGImageRelease(dstImage);
CGContextRelease(context);
CGColorSpaceRelease(grayColorSpace);
}
And this is my Window Controler:
#implementation HistogramControllerWindowController
#synthesize display;
- (id)initWithWindow:(NSWindow *)window{
self = [super initWithWindow:window];
if (self) {
// Initialization code here.
}
return self;
}
- (void)windowDidLoad
{
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
[super windowDidLoad];
histogramDisplayLayer = [CALayer layer];
[histogramDisplayLayer setContentsGravity:AVLayerVideoGravityResizeAspectFill];
histogramDisplayLayer.position = CGPointMake(display.frame.size.width/2., display.frame.size.height/2.);
[histogramDisplayLayer setNeedsDisplay];
[display.layer addSublayer: histogramDisplayLayer];
}
#end
And I'm calling this way:
[frame wrapImageToCALayer:histogramDisplayLayer];
Note that histogramDisplayLayer is an external (CALayer *)
Your histogramDisplayLayer have no size defined, you just set a position but not its size. So init its frame, and this should fix your problem.
Just insert:
[display setWantsLayer:YES]; // view's backing store is using a Core Animation Layer
Before:
[display.layer addSublayer: histogramDisplayLayer];
[ Best way to change the background color for an NSView ]
I'm very sure this is an easy fix, but it's so specific I don't know where to find an answer... I want to create a method that retrieves and uses a UIImage from the object it is performed on.
Here is the line that calls the method with the imageView as the object.
[self performSelector:#selector(method:) withObject:self.imageView afterDelay:0.0];
and this is the method...
- (void) method:(UIImage *) image {
if ([image isEqual:image1]){
x = 1;
}
if ([image isEqual:image2]){
x = 2;
}
if ([image isEqual:image3]){
x = 3;
}
if ([image isEqual:image4]){
x = 4;
}
if ([image isEqual:image5]){
x = 5;
}
...am I going about this in the right way? Thanks!
To retrive UIImage from UIImageView object you should just use
[self.imageView image];
And if you want to create a method that retrieves and uses a UIImage from the object it is performed on. (I will guess you are using an UIImageView object...) You should subclass or add category to the UIImageView and add a method like this to it
- (void) method {
UIImage* image = [self image];
//do whatever you want with the image
//...
}
As an alternative you can pass an UIImageView to the method and let it retrieve the image and then use it for whatever purpose
- (void) method:(UIImageView*)imageView {
UIImage* image = [imageView image];
//do whatever you want with the image
//...
}
Use this method
[self performSelector:#selector(method:) withObject:self.imageView.view afterDelay:0.0];
You are passing UIImageview as parameter rather than UIImage
This is what I did:
#implementation BGUIActivityIndicator
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
[self customInitialize];
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
[self customInitialize];
return self;
}
-(void)customInitialize
{
UIView * theImageView= [self findASubViewforClass:[UIImageView class]];//
UIImageView * theImageView1 = (UIImageView *) theImageView;
theImageView1.image = [UIImage imageNamed:#"spinner_blue"];
[theImageView1.image saveScreenshot];
while (false) ;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
Everything seems perfect. [theImageView1.image saveScreenshot]; jot down both the old and new view perfectly.
However, nothing changes. Why?
I am not exactly asking how to change the image of UIActivityIndicator. There are tons of it already. I want to use it by subclassing UIActivityIndicator because I think it's the most elegant solution. I can't seem to do that.
In particular, I am asking why my approach, which works for changing background of search controller, for example, doesn't work for this.
According to the UIActivityIndicatorView Class Reference ,there is no way/ chance to change the image through sub-classing.
However you can change its activityIndicatorViewStyle , color of the activity indicator,UIActivityIndicatorStyle etc..
I think, without sub-classing, the class class UIImageView provides a very useful and simple way to implement such a thing. The only thing you have to do is to:
1.Provide a number of images that reflect your indicator animation.
2.Create a new UIImageView instance and set images and animation duration.
3.Position your custom activity indicator within your current view.
SAMPLE CODE:
//Create the first status image and the indicator view
UIImage *statusImage = [UIImage imageNamed:#"status1.png"];
UIImageView *activityImageView = [[UIImageView alloc]
initWithImage:statusImage];
//Add more images which will be used for the animation
activityImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"status1.png"],
[UIImage imageNamed:#"status2.png"],
[UIImage imageNamed:#"status3.png"],
[UIImage imageNamed:#"status4.png"],
[UIImage imageNamed:#"status5.png"],
[UIImage imageNamed:#"status6.png"],
[UIImage imageNamed:#"status7.png"],
[UIImage imageNamed:#"status8.png"],
nil];
//Set the duration of the animation (play with it
//until it looks nice for you)
activityImageView.animationDuration = 0.8;
//Position the activity image view somewhere in
//the middle of your current view
activityImageView.frame = CGRectMake(
self.view.frame.size.width/2
-statusImage.size.width/2,
self.view.frame.size.height/2
-statusImage.size.height/2,
statusImage.size.width,
statusImage.size.height);
//Start the animation
[activityImageView startAnimating];
//Add your custom activity indicator to your current view
[self.view addSubview:activityImageView];
See the full details Here
I have an array of images which I want to animate by playing these images one after the other in a sequence. I want to repeat the whole loop several times. I am developing a game for iPad. Suggest to me a method to achieve this functionality in Objective-C with the Cocoa framework.
NSArray *animationArray = [NSArray arrayWithObjects:
[UIImage imageNamed:#"images.jpg"],
[UIImage imageNamed:#"images1.jpg"],
[UIImage imageNamed:#"images5.jpg"],
[UIImage imageNamed:#"index3.jpg"],
nil];
UIImageView *animationView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0,320, 460)];
animationView.backgroundColor = [UIColor purpleColor];
animationView.animationImages = animationArray;
animationView.animationDuration = 1.5;
animationView.animationRepeatCount = 0;
[animationView startAnimating];
[self.view addSubview:animationView];
[animationView release];
add your own images in the array.repeat Count 0 means infinite loop.You can give your own number also.
There are at least 3 ways to animate an array of images through a UIImageView. I'm adding 3 links to download sample code for the 3 possibilities.
The first one is the one that everyone knows. The other ones are less known.
- UIImageView.animationImages
Example Link
The problem of this one is that do not have Delegate to tell us in which moment the animation is finished. So, we can have problems if we want to display something after the animation.
In the same way, there is no possibility to kept the last image from the animation in the UIImageView automatically. If we combine both problems we can have a gap at the end of the animation if we want to kept the last frame on screen.
self.imageView.animationImages = self.imagesArray; // the array with the images
self.imageView.animationDuration = kAnimationDuration; // static const with your value
self.imageView.animationRepeatCount = 1;
[self.imageView startAnimating];
- CAKeyframeAnimation
Example Link
This way to animate works through CAAnimation. It have an easy delegate to use and we can know when the animation finish.
This is probably the best way to animate an array of images.
- (void)animateImages
{
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
keyframeAnimation.values = self.imagesArray;
keyframeAnimation.repeatCount = 1.0f;
keyframeAnimation.duration = kAnimationDuration; // static const with your value
keyframeAnimation.delegate = self;
// keyframeAnimation.removedOnCompletion = YES;
keyframeAnimation.removedOnCompletion = NO;
keyframeAnimation.fillMode = kCAFillModeForwards;
CALayer *layer = self.animationImageView.layer;
[layer addAnimation:keyframeAnimation
forKey:#"girlAnimation"];
}
Delegate:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (flag)
{
// your code
}
}
- CADisplayLink
Example Link
A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
This way to do it is really interesting and opens a lot of possibilities to manipulate what are we showing in screen.
DisplayLink getter:
- (CADisplayLink *)displayLink
{
if (!_displayLink)
{
_displayLink = [CADisplayLink displayLinkWithTarget:self
selector:#selector(linkProgress)];
}
return _displayLink;
}
Methods:
- (void)animateImages
{
self.displayLink.frameInterval = 5;
self.frameNumber = 0;
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
- (void)linkProgress
{
if (self.frameNumber > 16)
{
[self.displayLink invalidate];
self.displayLink = nil;
self.animationImageView.image = [UIImage imageNamed:#"lastImageName"];
self.imagesArray = nil;
return;
}
self.animationImageView.image = self.imagesArray[self.frameNumber++];
self.frameNumber++;
}
GENERAL PROBLEM:
Even though we have this 3 possibilities, if your animation is with a lot of big images, consider using a video instead. The usage of memory will decrease a lot.
A General problem you will face doing this is in the moment of the allocation of the images.
If you use [UIImage imageNamed:#"imageName"] you will have cahe problems.
From Apple:
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method locates and loads the image data from disk or asset catelog, and then returns the resulting object. You can not assume that this method is thread safe.
So, imageNamed: stores the image in a private Cache.
- The first problem is that you can not take control of the cache size.
- The second problem is that the cache did not get cleaned in time and if you are allocating a lot of images with imageNamed:, your app, probably, will crash.
SOLUTION:
Allocate images directly from Bundle:
NSString *imageName = [NSString stringWithFormat:#"imageName.png"];
NSString *path = [[NSBundle mainBundle] pathForResource:imageName
// Allocating images with imageWithContentsOfFile makes images to do not cache.
UIImage *image = [UIImage imageWithContentsOfFile:path];
Small problem:
Images in Images.xcassets get never allocated. So, move your images outside Images.xcassets to allocate directly from Bundle.
See the animationImages property of UIImageView. It’s hard to say if it fits your needs as you don’t give us details, but it’s a good start.
I have added a swift 3.0 extension for this
extension UIImageView {
func animate(images: [UIImage], index: Int = 0, completionHandler: (() -> Void)?) {
UIView.transition(with: self, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.image = images[index]
}, completion: { value in
let idx = index == images.count-1 ? 0 : index+1
if idx == 0 {
completionHandler!()
} else {
self.animate(images: images, index: idx, completionHandler: completionHandler)
}
})
}
}
Best solution for me use CADisplayLink. UIImageView doesn't have completion block and you can't catch steps of animation. In my task i must changing background of view with image sequencer step by step. So CADisplayLink allows you handling steps and finishing animation. If we talk about usage of memory, i think best solution load images from bundle and delete array after finishing
ImageSequencer.h
typedef void (^Block)(void);
#protocol ImageSequencerDelegate;
#interface QSImageSequencer : UIImageView
#property (nonatomic, weak) id <ImageSequencerDelegate> delegate;
- (void)startAnimatingWithCompletionBlock:(Block)block;
#end
#protocol ImageSequencerDelegate <NSObject>
#optional
- (void)animationDidStart;
- (void)animationDidStop;
- (void)didChangeImage:(UIImage *)image;
#end
ImageSequencer.m
- (instancetype)init {
if (self = [super init]) {
_imagesArray = [NSMutableArray array];
self.image = [self.imagesArray firstObject];
}
return self;
}
#pragma mark - Animation
- (void)startAnimating {
[self startAnimatingWithCompletionBlock:nil];
}
- (void)startAnimatingWithCompletionBlock:(Block)block {
self.frameNumber = 0;
[self setSuccessBlock:block];
self.displayLink.frameInterval = 5;
if (self.delegate && [self.delegate respondsToSelector:#selector(animationDidStart)]) {
[self.delegate animationDidStart];
}
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
-(void)stopAnimating {
self.image = [self.imagesArray lastObject];
[self.displayLink invalidate];
[self setDisplayLink:nil];
Block block_ = [self successBlock];
if (block_) {
block_();
}
if (self.delegate && [self.delegate respondsToSelector:#selector(animationDidStop)]) {
[self.delegate animationDidStop];
}
[self.imagesArray removeAllObjects];
}
- (void)animationProgress {
if (self.frameNumber >= self.imagesArray.count) {
[self stopAnimating];
return;
}
if (self.delegate && [self.delegate respondsToSelector:#selector(didChangeImage:)]) {
[self.delegate didChangeImage:self.imagesArray[self.frameNumber]];
}
self.image = self.imagesArray[self.frameNumber];
self.frameNumber++;
}
#pragma mark - Getters / Setters
- (CADisplayLink *)displayLink {
if (!_displayLink){
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(animationProgress)];
}
return _displayLink;
}
- (NSMutableArray<UIImage *> *)imagesArray {
if (_imagesArray.count == 0) {
// get images from bundle and set to array
}
return _imagesArray;
}
#end
This is a simple and working code for animation./
-(void)move
{
[UIView animateWithDuration:5
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
[_imgbox setFrame:CGRectMake(100, 100, _imgbox.frame.size.width, _imgbox.frame.size.height)];
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}