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.
Related
I have a parent VC with 3 children - Settings, Location and Diary.
Settings, Location & Diary are all accessed via IBActions based on their respective buttons.
When I go between Location and Diary, everything is fine. When I click Settings, it works, but when I click back to Location or Diary I get the dreaded must have a common parent view controller error. Funny thing is the exception says that the Diary and Location VC's don't share the same parent even though in that case I am clicking between Settings and either Diary or Location.
.m properties
#interface LPMainContentViewController ()
#property (weak, nonatomic) IBOutlet UIView *containerView;
#property (weak, nonatomic) IBOutlet UIButton *locationButton;
#property (weak, nonatomic) IBOutlet UIButton *diaryButton;
#property (weak, nonatomic) IBOutlet UIButton *settingsButton;
#property (strong, nonatomic) UIViewController *locationViewController;
#property (strong, nonatomic) UIViewController *diaryViewController;
#property (strong, nonatomic) UIViewController *settingsController;
- (IBAction)goToLocationView:(UIButton *)sender;
- (IBAction)goToDiaryView:(UIButton *)sender;
- (IBAction)goToSettings:(UIButton *)sender;
#end
I use if statements in the code to determine which was the previous child VC for the transition method and to set enabled on the button to YES or NO.
- (IBAction)goToLocationView:(UIButton *)sender {
if ([_diaryViewController isViewLoaded]){
[self cycleFromViewController:_diaryViewController toViewController:_locationViewController];
_locationButton.enabled = NO;
_diaryButton.enabled = YES;
}
else if ([_settingsController isViewLoaded]){
[self cycleFromViewController:_settingsController toViewController:_locationViewController];
_locationButton.enabled = NO;
_settingsButton.enabled = YES;
}
}
- (IBAction)goToDiaryView:(UIButton *)sender {
if ([_locationViewController isViewLoaded]){
[self cycleFromViewController:_locationViewController toViewController:_diaryViewController];
_locationButton.enabled = YES;
_diaryButton.enabled = NO;
}
else if ([_settingsController isViewLoaded]){
[self cycleFromViewController:_settingsController toViewController:_diaryViewController];
_diaryButton.enabled = NO;
_settingsButton.enabled = YES;
}
}
- (IBAction)goToSettings:(UIButton *)sender {
if ([_locationViewController isViewLoaded]){
[self cycleFromViewController:_locationViewController toViewController:_settingsController];
_locationButton.enabled = YES;
_settingsButton.enabled = NO;
}
else if ([_diaryViewController isViewLoaded]){
[self cycleFromViewController:_diaryViewController toViewController:_settingsController];
_diaryButton.enabled = YES;
_settingsButton.enabled = NO;
}
}
and here's the transition method pulled straight from the Apple docs
- (void) cycleFromViewController: (UIViewController*) oldVC
toViewController: (UIViewController*) newVC
{
[oldVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
newVC.view.frame = _containerView.bounds;
CGRect endFrame = _containerView.bounds;
[self transitionFromViewController: oldVC toViewController: newVC
duration: 0.0 options:0
animations:^{
newVC.view.frame = oldVC.view.frame;
oldVC.view.frame = endFrame;
}
completion:^(BOOL finished) {
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
}];
}
isViewLoaded is the wrong thing to check. The view will get loaded once and then stick around, so you're probably choosing the wrong "current" view controller after a few cycles. Have another property to hold the current view controller instead, it will simplify your code and also actually work:
#property (weak, nonatomic) UIViewController *currentViewController;
You'll need to set this to whatever the initial child view controller is when you first set up the container view controller.
Then, change the goToXX methods to be like the following:
- (IBAction)goToDiaryView:(UIButton *)sender {
self.settingsButton.enabled = YES;
self.locationButton.enabled = YES;
self.diaryButton.enabled = NO;
[self cycleFromViewController:self.currentViewController toViewController:self.diaryViewController];
}
Last of all, in your cycle method, make sure you keep this property updated in the completion block of the transition:
completion:^(BOOL finished) {
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
self.currentViewController = newVC;
}];
Note that it is safer to use your properties rather than the _instanceVariables. That's what they're there for.
I have a UIView without any associated UIViewController constantly animating with auto layout and layoutIfNeeded (see code bellow).
But when this view (which is contained in another view) disappears (for example when a modal view covers the view it is contained in) and after I dismiss this modal view, the animating view isn't animating anymore.
I managed to animate it back with the didMoveToWindow:animated method but i'm not sure this is the proper approach.
#interface AnimatingView()
#property (strong, nonatomic) UIView *aSubview;
#property (strong, nonatomic) NSLayoutConstraint *constraintTop;
#property (assign, nonatomic, getter = isStarted) BOOL started;
#property (assign, nonatomic) BOOL stopAnimation;
#end
#implementation AnimatingView
- (id)init
{
self = [super init];
if (self) {
self.stopAnimation = YES;
/* setting base auto layout constraints */
}
return self;
}
-(void)animate{
float aNewConstant = arc4random_uniform(self.frame.size.height);
[UIView animateWithDuration:ANIMATION_DURATION animations:^{
[self removeConstraint:self.constraintTop];
self.constraintTop = [NSLayoutConstraint constraintWithItem:self.aSubview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1 constant:aNewConstant];
[self addConstraint:self.constraintTop];
[self layoutIfNeeded];
} completion:^(BOOL finished) {
if (finished && !self.stopAnimation) {
[self animate];
}
}];
}
- (void)didMoveToWindow{
[super didMoveToWindow];
if ([self isStarted]) {
[self stop];
[self start];
}
}
-(void)start{
if (![self isStarted]) {
self.stopAnimation = NO;
[self setStarted:YES];
[self animate];
}
}
-(void)stop{
if ([self isStarted]) {
self.stopAnimation = YES;
[self setStarted:NO];
}
}
#end
You can always use a behavioral pattern : KVO to control the state of the UIView or what I would do, use a notification to animate the view when you want to. Both options are valid. You can have as well a delegate associated with this class and implement the behavior you want in your UIView.
Assuming your view is contained in another view which is contained in a Controller, you can set a BOOL variable called didReturnFromModalChildView which you set it true every time you present that modal view. When you dismiss that modal view, your application will call -(void)viewWillAppear:(BOOL)animated. Here you have to check if that BOOL variable is set to Yes and then use a public method calling if you have a reference to the view you want to animate, or a delegation process or observer-notification pattern. Don't forget to set didReturnFromModalChildView to false.
I hope this will help you.
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.
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?
I have a view controller (in a storyboard) with 6 buttons and 6 subviews. Those subviews are connected to IBOutlets and named like "mySubView1", "mySubView2" etc:
#property (weak, nonatomic) IBOutlet UIView *mySubView1;
#property (weak, nonatomic) IBOutlet UIView *mySubView2;
I want to animate the alpha value of the subview depending on which button was pressed. All 6 buttons are connected to the same IBAction (below) and each button has a tag (1 to 6).
How do I get the subview property name dynamically? eg, mySubView1 if sender.tag = 1 for example, in the animations block below:
- (IBAction)animateSubviews:(id)sender {
[UIView animateWithDuration:0.4
delay: 0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
self.mySubView1.alpha = 1;
}
completion:^(BOOL finished){
}];
}
so what I need is something like : self.mySubView[sender tag].alpha = 1 sort of thing, but I'm stumped, so any advice much appreciated.
Many thanks
1) Assigning tags to subviews
Assign tags to your UIViews, for example the view that corresponds to button with tag 1 could have tag 101.
- (IBAction)animateSubviews:(id)sender {
[UIView animateWithDuration:0.4
delay: 0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
UIView *subview = [self.view viewWithTag:((UIButton*)sender).tag + 100];
subview.alpha = 1;
}
completion:^(BOOL finished){
}];
}
2) Using dynamic selectors (which answers your original question)
You can use the method NSSelectorFromString which returns a SEL.
UIView *subview = [self performSelector:NSSelectorFromString([NSString stringWithFormat:#"mySubView%d", ((UIButton*)sender).tag])];
#property (strong, noatomatic) NSArray* arrayForSubViews;
if (!self.arrayForSubViews) {
self.arrayForSubViews = #[ self.mySubView1, self.mySubView2, ... ];
}
//get the second view
self.arrayForSubViews[1];