Animation is not being performed through to another view controller - objective-c

I am trying to trigger an animation in one view, based on what is happening in a separate class file. It gets to the method, supported by the fact that it does spit out the two NSLog statements, but doesn't commit the UIView Animation
Here is the code:
ViewController.h
#interface ViewController : UIViewController {
}
-(void)closeMenu;
ViewController.m
-(void)closeMenu{
//close the menu
NSLog(#"Got here");
[UIView animateWithDuration:0.6 animations:^{
[UIView setAnimationBeginsFromCurrentState:YES]; // so it doesn't cut randomly, begins from where it is
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
}];
NSLog(#"Got here2");
}
OtherClass.m (commented code may be irrelevant to this question, of course still used in actual application. Jut thought it might make it easier for comprehension)
#import "ViewController.h"
...
//- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
//{
ViewController *foo = [[[ViewController alloc] init] autorelease];
//SelectableCellState state = subItem.selectableCellState;
//NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
//switch (state) {
//case Checked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
//close the menuView
[foo closeMenu];
//break;
//case Unchecked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
//break;
//default:
//break;
//}
}

You're mixing old fashioned animation, with block based animation. For example, in the documentation, it states for setAnimationBeginsFromCurrentState:
Use of this method is discouraged in iOS 4.0 and later. Instead, you
should use theanimateWithDuration:delay:options:animations:completion:
method to specify your animations and the animation options.
I'm not 100% sure if this is supported. You should change your animation code to this at least:
[UIView animateWithDuration:0.6
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut
animations:^{
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
}
completion:nil];
Apart from that, it seems like it should work. It may cause issues if anything else if affecting the frame. It may be worth calculating the frame before the animation block. A'la:
CGRect newFrame = CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)
[UIView animateWithDuration...:^{ menuView.frame = newFrame; }...];
EDIT: Oh wait, looks like you're alloc/init'ing the object in (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem, and calling the method on it, but the view is nowhere in the view hierarchy. You need to call the animation on an instance which is being displayed on screen. Hope that makes sense?
EDIT 2: To call it on an instance which is already being displayed, typically you need to store it in an instance variable. I can't say exactly how in your situation, but generally it'd be of the form:
#interface OtherClass () {
ViewController* m_viewController;
}
#end
....
- (void)viewDidLoad // Or where ever you first create your view controller
{
...
// If you're adding a ViewController within another ViewController, you probably need View Controller Containment
m_viewController = [[ViewController alloc] init];
[self addChildViewController:m_viewController];
[m_viewController didMoveToParentViewController:self];
[self.view addSubview:m_viewController.view];
...
}
// If you're using ARC code, the dealloc wouldn't typically be necessary
- (void)dealloc
{
[m_viewController release];
[super dealloc];
}
//- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
//{
//SelectableCellState state = subItem.selectableCellState;
//NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
//switch (state) {
//case Checked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
//close the menuView
[m_viewController closeMenu];
//break;
//case Unchecked:
//NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
//break;
//default:
//break;
//}
}
If you need to access it from outside the class, this won't be sufficient, use properties for that. I.e.
Header File
#property (nonatomic, strong) ViewController* myViewController
.m file
// Use it as such
[self.myViewController closeMenu];

That animation code is really strange. You are mixing the new and the old UIView animation code and I don't think you can do that (but I could be wrong).
Since your have begun using the block based API I would recommend going that route (Apple recommends the same thing).
There is a similar method to the one you've used that takes options called animateWithDuration:delay:options:animations:completion:. You can pass in 0 for the delay and an empty block that takes a BOOL for the completion.
The two flags you want to pass for the options are UIViewAnimationOptionBeginFromCurrentState and UIViewAnimationOptionCurveEaseInOut.
Your code would look something like this
[UIView animateWithDuration:0.6
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut
animations:^{
// set your frame here...
[menuView setFrame:CGRectMake(menuView.frame.origin.x,
-menuView.frame.size.height,
menuView.frame.size.width,
menuView.frame.size.height)];
} completion:^(BOOL finished){}];

Related

Xcode os x : Why drawRect could not access instance data

I am trying to draw something in my custom view, but not sure why drawRect could not access its instance data. Here is the steps I tried.
Create a Mac OS X app, with using storyboard checked.
In the storyboard, delete the view, then add a new custom view under the view at the same place. (I tried if the view is not deleted, same).
Assign EEGView class to the newly added custom view.
then run. From the log information, you will notice that the drawRect could not access the instance data although the instance variables get initialized and updated.
In viewCtroller.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
myView = [[EEGView alloc] init];
//[self.view addSubview:myView];
//Start Timer in 3 seconds to show the result.
NSTimer* _timerAppStart = [NSTimer scheduledTimerWithTimeInterval:2
target:self
selector:#selector(UpdateEEGData)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timerAppStart forMode:NSDefaultRunLoopMode];
}
- (void)UpdateEEGData
{
// NSLog(#"UpdateEEGData.....1");
// myView.aaa = 200;
// myView.nnn = [NSNumber numberWithInteger:myView.aaa];
// make sure this runs on the main thread
if (![NSThread isMainThread]) {
// NSLog(#"NOT in Main thread!");
[self performSelectorOnMainThread:#selector(updateDisplay) withObject:nil waitUntilDone:TRUE];
}else
{
[self.view setNeedsDisplay:YES];
}
NSLog(#"UpdateEEGData.....2");
[myView setAaa:400];
myView.nnn = [NSNumber numberWithInteger:myView.aaa];
// make sure this runs on the main thread
if (![NSThread isMainThread]) {
// NSLog(#"NOT in Main thread!");
[self performSelectorOnMainThread:#selector(updateDisplay) withObject:nil waitUntilDone:TRUE];
}else
{
[self.view setNeedsDisplay:YES];
}
}
-(void)updateDisplay
{
[self.view setNeedsDisplay:YES];
}
In my custom view class EEGView.m
#implementation EEGView
#synthesize aaa;
#synthesize nnn;
-(id)init{
self = [super init];
if (self) {
aaa = 10;
nnn = [NSNumber numberWithInteger:aaa];
NSLog(#"init aaa: %i", aaa);
NSLog(#"init nnn: %i", [nnn intValue]);
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
NSLog(#"drawRect is here");
NSLog(#"drawRect aaa: %i", aaa);
NSLog(#"drawRect nnn: %i", [nnn intValue]);
}
#end
Did I miss anything? Tested in Xcode 7.2 & 7.2. But if I leave the 'using storyboard' unchecked, it works.
Or is it a Xcode bug?
Any advice appreciated.
Thanks in advance.
If you've added EEGView view on storyboard, you shouldn't be also instantiating one in viewDidLoad. You've alloc/init'ed a new one, but it bears no relationship to the one that the storyboard created for you. So, the one created by the storyboard has drawRect called, but you're setting the properties in the separate instance that you created in viewDidLoad which was never added to the view hierarchy (and thus never will have its drawRect called).
When the storyboard instantiates the view controller's view, it will instantiate your EEGView for you. All you need to do is to hook up an IBOutlet for this view in order to get a reference to it from your view controller. (For example, you can control drag from the EEGView in the storyboard scene to the #interface for the view controller that you've pulled up in the assistant editor.)

Cause viewDidLoad to run

I'm creating a new UIViewController but waiting a few seconds before I'm showing it.
The thing is I want viewDidLoad to be called before I'm actually showing the now object. How can I make viewDidLoad run?
Currently I'm doing something like this:
UIView *tempView = newObject.view;
But I really don't like that solution.
If there is code that you want to run that you want to be called when the view is instantiated then just put it in the init method.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName: nibNameOrNil
bundle: nibBundleOrNil];
if (self)
{
// Custom stuff
}
return self;
}
Is there another reason why you are wanting viewDidLoad to be called?
Looks like just calling [newObject view]; does the trick!
- (void) viewDidLoad {
[super viewDidLoad];
self.view.alpha = 0; //When alpha = 0, it is also hidden
[UIView animateWithDuration:2.0f animations:^{
// Nothing to animate here
} completion:^(BOOL finished) {
// On animation completion show the content of the view, also animated.
[UIView animateWithDuration:0.5f animations:^{
self.view.alpha = 1;
}];
}];
}
The viewDidLoad is always called before the object is shown in screen.
If you want some delay , you can set some delay code inside viewDidLoad or viewDidAppear. But why exactly do you need the delay , there may be other solutions to solve your issue.

Why is my UIViewController being loaded twice? iOS7

I am presenting my custom UIViewController (called "temp") with a custom animation. The UIVC gets called with:
[temp setModalPresentationStyle:UIModalPresentationCustom];
temp.transitioningDelegate = self;
[temp.view setHidden:YES];
[self presentViewController:temp animated:YES completion:nil];
My custom animation is presenting a view modally from right to top-left position of the screen. It is being presented hidden so the user doesn't see the animation. After it reaches the SCREEN_HEIGHT (768) position it is being set to visible and animated (moved) from top to bottom being presented in the middle. The goal was to present a view from top to bottom and dismiss it from top to bottom (like a movie scene). This code is the NOT working one:
- (void)animateTransition:(id)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
NSLog(#" fromViewController %# ",fromViewController);
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
NSLog(#" toViewController %# ",toViewController);
UIView *containerView = [transitionContext containerView];
if (self.presenting)
{
// set starting rect for animation toViewController.view.frame = [self rectForDismissedState:transitionContext];
[containerView addSubview:toViewController.view];
[UIView animateWithDuration:0.5
animations:^{
toViewController.view.frame = CGRectMake(-self.customSize.width, self.yValue, self.customSize.width, self.customSize.height);
}
completion:^(BOOL finished)
{
//HERE IS THE PROBLEM!!!
[toViewController.view setHidden:NO];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
CGRect variable = [self rectForPresentedState:transitionContext];
CGRect fitToCurrentScreenResolution = CGRectMake(0, 0, variable.size.width, variable.size.height);
toViewController.view.frame = fitToCurrentScreenResolution;
}
completion:^(BOOL finished)
{
[transitionContext completeTransition:YES];
}];
}];
}
else
{
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
fromViewController.view.frame = [self rectForDismissedState:transitionContext];
}
completion:^(BOOL finished)
{
[transitionContext completeTransition:YES];
[fromViewController.view removeFromSuperview];
}
];
}
}
And here is the solution:
- (void)animateTransition:(id)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
NSLog(#" fromViewController %# ",fromViewController);
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
NSLog(#" toViewController %# ",toViewController);
UIView *containerView = [transitionContext containerView];
if (self.presenting)
{
// set starting rect for animation toViewController.view.frame = [self rectForDismissedState:transitionContext];
[containerView addSubview:toViewController.view];
[UIView animateWithDuration:0.5
animations:^{
toViewController.view.frame = CGRectMake(-self.customSize.width, self.yValue, self.customSize.width, self.customSize.height);
}
];
[toViewController.view setHidden:NO];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
CGRect variable = [self rectForPresentedState:transitionContext];
CGRect fitToCurrentScreenResolution = CGRectMake(0, 0, variable.size.width, variable.size.height);
toViewController.view.frame = fitToCurrentScreenResolution;
}
completion:^(BOOL finished)
{
[transitionContext completeTransition:YES];
}];
}
else
{
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
fromViewController.view.frame = [self rectForDismissedState:transitionContext];
}
completion:^(BOOL finished)
{
[transitionContext completeTransition:YES];
[fromViewController.view removeFromSuperview];
}
];
}
}
My question is simple. Why is my UIVC being presented twice?
I have tried making my custom UIVC a property which is lazy loaded but my app crashes saying that a UIVC = nil can not be presented modally.
I have tried this solution, but it didn't apply to my problem :viewWillAppear being called twice in iOS5
I also did this with no help: Calling presentModalViewController twice?
I could have used a hack but I wouldn't find out why it is happening. So far it seems that when the animation enters the completion BLOCK it calls the view again.
The apple docs say:
A block object to be executed when the animation sequence ends. This
block has no return value and takes a single Boolean argument that
indicates whether or not the animations actually finished before the
completion handler was called. If the duration of the animation is 0,
this block is performed at the beginning of the next run loop cycle.
This parameter may be NULL.
Is the view being drawn again since the next run loop cycle is being started?
NOTE: Even thought the view is being presented twice, the viewDidLoad method is being called only once.
I would like to know why this is happening. There are some stackoverflow questions with the same code but with different usage scenarios having the same problem without a working solution or explanation.
Thank you for any advice/comment.
iOS 8.0, Xcode 6.0.1, ARC enabled
Yeah you are definetely onto it with "chained animation" (see comment from O.P.).
I witnessed a similar problem trying to hide and show the UIStatusBar for various UIViewControllers in my application, e.g. I have a dummy after load screen UIViewController that shows the same image as the load screen, but it has some added animations.
I am using a custom transition, which features a UIViewController that handles the transition from the "from" UIViewController and the "to" UIViewController by adding or removing their views from itself and assigning the "to" UIViewController "control" to itself. So on and so forth.
In the app. delegate,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
I had to instantiate the "initial view controller" and then initialize the transition UIViewController with it. Since there are no "to" UIViewControllers the transition UIViewController must hold an initial UIView of the UIViewController it was initialized with until a transition is triggered.
This was done utilizing,
self.window.rootViewController = self.transitionViewController;
[self.window makeKeyAndVisible];
After the very first transition, there always two UIViews overlaid onto each other. And two UIViewControllers one existing as the current control for the transition UIViewController that was assigned during the transition and the previous UIViewController that remains until the transition completes.
This was the code I was trying to use to show/hide the UIStatusBar, one must have the "View controller-based status bar appearance" set to "YES" in the *-Info.plist file.
- (void)viewDidLoad
{
[self performSelector:#selector(setNeedsStatusBarAppearanceUpdate)];
}
- (BOOL)prefersStatusBarHidden
{
return false;
}
Whenever the "return" value was changes from default "false" to "true" regardless of when
[self performSelector:#selector(setNeedsStatusBarAppearanceUpdate)];
was triggered, delay, no delay, conditional, etc.; both UIViewControllers, the "to" and "from" were reloaded. At first this was not noticeable, however after implementing an NSURLSession in one of the UIViewControllers that was triggered in the - (void)ViewDidLoad; the problem was clear. The session was executed twice and the graphical content involved was also updated.
I successfully solved the issue in two ways, however I kept the 2nd.
I put everything in -(void)ViewDidLoad; in an if statement and forced it to only be executed once, using an instance variable boolean. The -(void)ViewDidLoad; still loaded twice, however, things that I did not want to execute twice did not.
I transitioned to the UIViewController at which the UIStatusBar hidden state needed to change without using my transitional UIViewController. After the UIStatusBar was shown or hidden, I would reset the "rootViewController" for the app. delegate, once again assigning the transitional UIViewController as always "shown".
I describe how to do this in the following post: https://stackoverflow.com/a/26403108/4018041
Thanks. Hope this helps someone. Please comment on how this could be handled in the future for either the OP or myself.

Reading touch events in a QLPreviewController

I've got a QuickLook view that I view some of my app's documents in. It works fine, but I'm having my share of trouble closing the view again. How do I create a touch event / gesture recognizer for which I can detect when the user wants to close the view?
I tried the following, but no events seem to trigger when I test it.
/------------------------ [ TouchPreviewController.h ]---------------------------
#import <Quicklook/Quicklook.h>
#interface TouchPreviewController : QLPreviewController
#end
//------------------------ [ TouchPreviewController.m ]---------------------------
#import "TouchPreviewController.h"
#implementation TouchPreviewController
- (id)init:(CGRect)aRect {
if (self = [super init]) {
// We set it here directly for convenience
// As by default for a UIImageView it is set to NO
UITapGestureRecognizer *singleFingerDTap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleSingleDoubleTap:)];
singleFingerDTap.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:singleFingerDTap];
[self.view setUserInteractionEnabled:YES];
[self.view setMultipleTouchEnabled:YES];
//[singleFingerDTap release];
}
return self;
}
- (IBAction)handleSingleDoubleTap:(UIGestureRecognizer *) sender {
CGPoint tapPoint = [sender locationInView:sender.view.superview];
[UIView beginAnimations:nil context:NULL];
sender.view.center = tapPoint;
[UIView commitAnimations];
NSLog(#"TouchPreviewController tap!" ) ;
}
// I also tried adding this
- (BOOL)gestureRecognizer:(UIGestureRecognizer *) gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*) otherGestureRecognizer {
return YES;
}
#end
Edit: For clarification, this is how I instantiate the controller:
documents = [[NSArray alloc] initWithObjects: filename , nil ] ;
preview = [[TouchPreviewController alloc] init];
preview.dataSource = self;
preview.delegate = self;
//set the frame from the parent view
CGFloat w= backgroundViewHolder.frame.size.width;
CGFloat h= backgroundViewHolder.frame.size.height;
preview.view.frame = CGRectMake(0, 0,w, h);
//refresh the preview controller
[preview reloadData];
[[preview view] setNeedsLayout];
[[preview view] setNeedsDisplay];
[preview refreshCurrentPreviewItem];
//add it
[quickLookView addSubview:preview.view];
Also, I've defined the callback methods as this:
- (NSInteger) numberOfPreviewItemsInPreviewController: (QLPreviewController *) controller
{
return [documents count];
}
- (id <QLPreviewItem>) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index
{
return [NSURL fileURLWithPath:[documents objectAtIndex:index]];
}
Edit2: One thing i noticed. If I try making swiping gestures, I get the following message. This could shed some light on what is wrong/missing?
Ignoring call to [UIPanGestureRecognizer setTranslation:inView:] since
gesture recognizer is not active.
I think your example code is incomplete. It isn't clear how you are instantiating the TouchPreviewController (storyboard, nib file or loadView.)
I have never used the class so I could be way out in left field.
If you've already instantiated a UITapGestureRecognizer in the parent viewController, it is absorbing the tap events and they aren't passed on to your TouchPreviewController.
I would implement the view hierarchy differently by attaching the UITapGestureRecognizer to the parent viewController and handle presentation and unloading of the QLPreviewController there.
I think you might not have to subclass QLPreviewController by instantiating the viewController from a nib file.
When your parent viewController's UITapGestureRecognizer got an event you would either push the QLPreviewController on the navigation stack or pop it off the navigation stack when done.
Hope this is of some help.

Setting Bool in different classes

I have the following code where after a bool is true I want to add a drawing to my rect. here is the code I have but for some reason it is either not setting the bool or calling the setNeedsDisplay. Am I referencing to the other class properly? thanks
//in AppController.m
-(IBAction)colorToggle:(id)sender
{
if ([colorFilter state] == NSOnState)
{
CutoutView *theView = [[CutoutView alloc] init];
[theView setFilterEnabled:YES];
}
}
//in cutoutView.m
- (void)drawRect:(NSRect)dirtyRect
{
[[[NSColor blackColor]colorWithAlphaComponent:0.9]set];
NSRectFill(dirtyRect);
//this is what i want to be drawn when my bool is true and update the drawRect
if (filterEnabled == YES) {
NSRectFillUsingOperation(NSMakeRect(100, 100, 300, 300), NSCompositeClear);
[self update];
}
}
-(void)update
{
[self setNeedsDisplay:YES];
}
OK, you know how not every UILabel is the same? Like, you can remove one UILabel from a view without all the others disappearing too? Well, your CutoutView is the same way. When you write CutoutView *theView = [[CutoutView alloc] init]; there, that creates a new CutoutView that isn't displayed anywhere. You need to talk to your existing CutoutView (probably by hooking up an outlet, but there are any number of perfectly valid designs that will accomplish this goal).
You are forgetting to call the drawRect: method, it should looks like this:
CutoutView *theView = [[CutoutView alloc] init];
[theView setFilterEnabled:YES];
[theView setNeedsDisplay];
From the docs:
When the actual content of your view changes, it is your
responsibility to notify the system that your view needs to be
redrawn. You do this by calling your view’s setNeedsDisplay or
setNeedsDisplayInRect: method of the view.