I am attempting to cross-fade from one UIImage to another UIImage which are contained in a UIImageView. This seems like it should be accomplished with UIView Animation with the following code:
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic, strong) IBOutlet UIImageView *sunImageView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_sunImageView.image = [UIImage imageNamed:#"sun1.png"];
UIImage *image = [UIImage imageNamed:#"sun3.png"];
[UIView transitionWithView:self.view
duration:3.0f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
_sunImageView.image = image;
// _sunImageView.alpha = 0;
} completion:^(BOOL finished){
NSLog(#"FINISHED");
}];
}
The IBOutlet is wired up correctly as the commented out code which animates the .image alpha from 1 to 0 works fine. When this code runs, sun3.png is the image which is presented. sun1.png, even though it is set prior to the animation is never displayed.
For some reason, this code does not transition from sun1.png to sun3.png. I have searched quite a bit and this is the suggested solution for cross-dissolving images in an image view, but it doesn't seem to be working for me. Any thoughts would be appreciated.
Try putting animation code in viewDidAppear: method.
Related
I'm very new to iOS programming and I have two simple UIView's.
.h file:
#property (strong, nonatomic) IBOutlet UIView *mainPageOutlet;
#property (strong, nonatomic) IBOutlet UIView *secondPageOutlet;
.m file:
- (IBAction)secondPageToMain:(id)sender {
self.view = [self mainPageOutlet];
}
- (IBAction)mainPageToSecondPage:(id)sender {
self.view = [self secondPageOutlet];
}
How can I use animations or transitions when switching between these two views? Currently they only appear when the button is clicked. Ideally I'd like to have something like push, modal, slide in or up.
Is there a way of doing this?
Many thanks.
Use following code.
- (IBAction)secondPageToMain:(id)sender {
[UIView transitionFromView:[self secondPageOutlet]
toView:[self mainPageOutlet]
duration:2
options: UIViewAnimationOptionTransitionFlipFromRight
completion:^(BOOL finished){
[[self secondPageOutlet] sendSubviewToBack];
}];
}
- (IBAction)mainPageToSecondPage:(id)sender {
[UIView transitionFromView:[self mainPageOutlet]
toView:[self secondPageOutlet]
duration:2
options: UIViewAnimationOptionTransitionFlipFromRight
completion:^(BOOL finished){
[[self mainPageOutlet] sendSubviewToBack];
}];
}
And in your viewDidLoad method add following code,
[[self secondPageOutlet] sendSubviewToBack];
So initially your second view will be behind your main view and on performing action it will come in front with animation.
I've really been struggling, maybe since I'm a newbie into Objective-C, to be able to perform the following.
Controller MAViewControllerMenu (controller 1) holds a button that allows to select a picture from the library
Controller MAViewControllerPictureDisplay (controller 2) holds an imageView (bigImageView)
Once I select a picture from the picker in controller 1, I want to move to the next window (controller 2) and there, have the imageView holding my selected picture.
Problems:
1. It wouldn't show the next controller
2. it wouldn't change the imageView to hold my selected picture
Observations:
1. When I have the imageView on controller 1 rather than 2, it holds the picture just like I want it to
2. The debugger shows that the function to change imageView in controller 2's class is invoked. However, nothing changes, even when I tried to simply change a label.
Code:
MAViewControllerMenu.h
#interface MAViewControllerMenu : UIViewController <UIImagePickerControllerDelegate,UINavigationControllerDelegate>
{
UIImagePickerController *picker1;
UIImagePickerController *picker2;
UIImage *image;
IBOutlet UIImageView *imageView;//THIS CHANGES,BUT IS IN CONTROLLER 1
}
- (IBAction)TakePhoto;
- (IBAction)ChooseExisting;
- (IBAction)movetonext;
MAViewControllerPictureDisplay.h
#import "MAViewControllerMenu.h"
#interface MAViewControllerPictureDisplay : UIViewController <UIImagePickerControllerDelegate,UINavigationControllerDelegate,menuControllerDelegate>
{
}
#property (weak, nonatomic) IBOutlet UIImageView *bigImageView;//THIS ONE WOULDNT HOLD THE CHOSEN IMAGE
#property (weak, nonatomic) IBOutlet UILabel *theLabel;
#property(nonatomic, strong) UIImage *image;
#end
implementation
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//load next page
self.bigImageView.image = self.image;
}
method to react to picture selection at MAViewControllerMenu.m
#import "MAViewControllerMenu.h"
#import "MAViewControllerPictureDisplay.h"
#interface MAViewControllerMenu ()
#end
#implementation MAViewControllerMenu
MAViewControllerPictureDisplay* imageControllerView;
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
image = [info objectForKey:UIImagePickerControllerOriginalImage];
[imageView setImage:image]; //WORKS ON CURRENT CONTROLLER
//imageControllerView.theLabel.text = #"hhdhdhd"; //DIDNT WORK
[imageControllerView.bigImageView setImage:image]; //UPDATE IMAGE AT CONTROLLER 2, DIDNT WORK
[self dismissViewControllerAnimated:YES completion:NULL];
[self movetonext];
}
- (IBAction)movetonext{
[self presentViewController:imageControllerView animated:YES completion:nil];//MOVE TO NEXT WINDOW- DOESNT WORK WHEN INVOKED AFTER SELECTING A PICTURE. DOES WORK WHEN INVOKING,SAY, AFTER A BUTTON WAS CLICKED
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//load next page
imageControllerView = [[self storyboard] instantiateViewControllerWithIdentifier:#"chosenImageController"];
}
The picture does not show up in the second view controller because the image view you are setting an image does not exist yet at this point.
[imageControllerView.bigImageView setImage:image];
Declare a UIImage property and pass the image using it. Then in imageControllerView's viewDidLoad show it in a UIImageView. Example:
#property(nonatomic, strong) UIImage *image;
- (void)viewDidLoad
{
[super viewDidLoad];
_bigImageView.image = _image;
}
Don't do that. You should treat another view controller's views as private. Reecon shows the correct way to do this (set up a property to hold the information, then display that info in viewDidLoad or vieWillAppear:animated)
This question already has answers here:
How to perform Callbacks in Objective-C
(5 answers)
Closed 9 years ago.
I can't figure out how to make a function, that will give information to the parent object, that has done something, so I need your help.
Example what I want to make:
ViewController class instantiate class BottomView and adds it as it's subview.
Make a call on instance of BottomView class to start animate something.
After the animation ends, I want to give a sign that the animation has ended to the ViewController class, that it could release/remove an instance BottomView from itself.
I need something like a callback.
Could you help please?
You could do something like this
#interface BottomView : UIView
#property (nonatomic, copy) void (^onCompletion)(void);
- (void)start;
#end
#implementation BottomView
- (void)start
{
[UIView animateWithDuration:0.25f
animations:^{
// do some animation
} completion:^(BOOL finished){
if (self.onCompletion) {
self.onCompletion();
}
}];
}
#end
Which you would use like
BottomView *bottomView = [[BottomView alloc] initWithFrame:...];
bottomView.onCompletion = ^{
[bottomView removeFromSuperview];
NSLog(#"So something when animation is finished and view is remove");
};
[self.view addSubview:bottomView];
[bottomView start];
1.You can do it with blocks!
You can pass some block to BottomView.
2. Or you can do it with target-action.
you can pass to BottomView selector #selector(myMethod:) as action,
and pointer to the view controller as target. And after animation ends
use performeSelector: method.
3. Or you can define delegate #protocol and implement methods in your viewController,
and add delegate property in BottomView.
#property (assign) id delegate;
If you make some animations in your BottomView, you can use
UIView method
animateWithDuration:delay:options:animations:completion:
that uses blocks as callbacks
[UIView animateWithDuration:1.0f animations:^{
}];
update:
in ButtomView.h
#class BottomView;
#protocol BottomViewDelegate <NSObject>
- (void)bottomViewAnimationDone:(BottomView *) bottomView;
#end
#interface BottomView : UIView
#property (nonatomic, assign) id <BottomViewDelegate> delegate;
.....
#end
in ButtomView.m
- (void)notifyDelegateAboutAnimationDone {
if ([self.delegate respondsToSelector:#selector(bottomViewAnimationDone:)]) {
[self.delegate bottomViewAnimationDone:self];
}
}
and after animation complit you should call [self notifyDelegateAboutAnimationDone];
you should set you ViewController class to confirm to protocol BottomViewDelegate
in MyViewController.h
#interface MyViewController : UIViewController <BottomViewDelegate>
...
#end
and f.e. in viewDidLoad you should set bottomView.delegate = self;
if you specifically want to just run a function after an animation has occurred you could get away with just using this:
[UIView animateWithDuration:0.5 animations:^{
//do animations
} completion:^(BOOL finished){
//do something once completed
}];
Since you are using animations, a more specific way to handle it is to use animation delegates. You can set the delegate of your animation to the viewcontroller so that after the animation ends below method will be called in your viewcontroller.
-(void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
To do that in the 2nd step you mentioned you should pass viewcontroller as an extra parameter so there in you can set the animation delegate.
Many thanks for all of you. I have tried two ways, by selector and by a delegate.
It's cool and I suppose, that I understand those mechanisms.
I have found one odd thing:
ViewController.h
#import <UIKit/UIKit.h>
#import "BottomPanelView.h"
#interface ViewController : UIViewController <BottomPanelViewDelegate>
#property (nonatomic, assign) BottomPanelView* bottomPanelView;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize bottomPanelView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
bottomPanelView = [[BottomPanelView alloc] initWithFrame:CGRectMake(0, 480, 320, 125)];
[bottomPanelView setDelegate:self];
[self.view addSubview: bottomPanelView];
[bottomPanelView start];
}
//delegate function
- (void)bottomViewAnimationDone:(BottomPanelView *)_bottomPanelView
{
NSLog(#"It Works!");
[bottomPanelView removeFromSuperview]; //1) Does it clears memory?
[bottomPanelView release]; //2) Strange is that if I have released it and set pointer to nil and use it below in touchesBegan it doesn't crashes. Why?
// but if I release it and don't set to nil, it will crash. Why reading from nil is okay and gives back 0 (myValue was set to 15)?
bottomPanelView = nil;
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Touched!");
NSLog(#"Pointer: %p", bottomPanelView);
NSUInteger val = [bottomPanelView myValue];
NSLog(#"myValue: %d", val);
}
My questions are in comments in code above.
Please, tell me especially why reading from nil pointer doesn't crashes the application?
Into the CALayer world:
I am creating a layer that needs to remain in middle of view regardless of device orientation. Can someone tell me why does my layer animates after rotation from the old position even though I removed it from superlayer? I understand that the frame and borderWidth properties are animatable but are they animatable even after removal from superLayer?
And if removal from superLayer does not reset the layer properties because the layer object has not been released (ok I can understand that), how do I mimic the behavior of a newly displayed layer so that the border does not shows like it is moving from an old position after rotation.
I created this sample project - cut and paste if you wish. You will just need to link the quartz core library.
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#property (nonatomic,strong) CALayer *layerThatKeepAnimating;
#end
#implementation ViewController
-(CALayer*) layerThatKeepAnimating
{
if(!_layerThatKeepAnimating)
{
_layerThatKeepAnimating=[CALayer layer];
_layerThatKeepAnimating.borderWidth=2;
}
return _layerThatKeepAnimating;
}
-(void) viewDidAppear:(BOOL)animate
{
self.layerThatKeepAnimating.frame=CGRectMake(self.view.bounds.size.width/2-50,self.view.bounds.size.height/2-50, 100, 100);
[self.view.layer addSublayer:self.layerThatKeepAnimating];
}
-(void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self.layerThatKeepAnimating removeFromSuperlayer];
}
-(void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
self.layerThatKeepAnimating.frame=CGRectMake(self.view.bounds.size.width/2-50,self.view.bounds.size.height/2-50, 100, 100);
[self.view.layer addSublayer:self.layerThatKeepAnimating];
}
#end
As odd as this sounds, the answer is to move code in
willRotateToInterfaceOrientation
to
viewWillLayoutSubviews
-(void) viewWillLayoutSubviews
{
self.layerThatKeepAnimating.frame=CGRectMake(self.view.bounds.size.width/2-50,self.view.bounds.size.height/2-50, 100, 100);
[self.view.layer addSublayer:self.layerThatKeepAnimating];
}
It looks like any layer "redrawing" here happens without animation, even if layer properties are animatable.
Well the problem is not what you think; when you remove that layer from the superview it is not actually nillified because you are retaining a strong reference to it. Your code doesn't enter the if statement in the getter for creating a fresh layer, because it is never nil after the first time:
if(!_layerThatKeepAnimating)
{
_layerThatKeepAnimating=[CALayer layer];
_layerThatKeepAnimating.borderWidth=2;
}
So either change your reference to layer in vc to weak:
#property (nonatomic, weak) CALayer * layerThatKeepAnimating;
Or delete it explicitly by:
-(void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self.layerThatKeepAnimating removeFromSuperlayer];
self.layerThatKeepAnimating = nil;
}
I will suggest you to use the first option because as you add layers (or subviews) to views you already earn one strong reference. That's why it is always recommended do it like this:
#property (weak, nonatomic) IBOutlet UIView *view;
but not (strong, nonatomic).
[self.sublayerToRemove removeFromSuperlayer];
self.sublayerToRemove = nil;
.h
IBOutlet UIImageView *photoImageView;
UIImage *photo;
.m (in viewWillAppear)
photoImageView.image = photo;
After view appeared, I can't change photoImageView by using:
photoImageView.image = [UIImage imageNamed:#"XXX.png"];
but changing the value of photo works.
photo = [UIImage imageNamed:#"XXX.png"];
How does it work?
Try this code.
//YourController.h
#interface YourController : UIViewController
{
UIImageView* _photoImageView;
}
#property (retain, nonatomic) IBOutlet UIImageView *photoImageView;
- (IBAction)changeImageAction:(id)sender;
#end
//YourController.m
#synthesize photoImageView = _photoImageView;
- (void)viewDidLoad
{
[super viewDidLoad];
self.photoImageView.image = [UIImage imageNamed:#"img.png"];
}
- (IBAction)changeImageAction:(id)sender {
self.photoImageView.image = [UIImage imageNamed:#"img_2.png"];
}
Some notes.
First of all, you have to link your photoImageView IBOutlet correctly in XCode. For test purposes I created a test button (UIButton) that is linked to an IBAction. The UIImageView and the test button (I created both with IB) are childs of YourController view.
Remember to import your images into your project and to release memory in dealloc and in viewDidUnload.
- (dealloc)
{
[_photoImageView release];
_photoImageView = nil;
[super dealloc]
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.photoImageView = nil;
}
Hope it helps.
Edit
The code is not ARC oriented but with few modifications it is possible to run it also with ARC.