NSViewAnimation for NSTableView Not Working on Mavericks - objective-c

I have a laptop that runs OSX 10.8, which is what I have been using to develop an app. I went to test the app on my desktop -- which runs 10.9 -- today and found that everything works the same except one animation.
I have an intro screen and want that to fade out and fade in the next screen; however, the second screen that fades in has an NSTableView on it. On 10.8 the table view fades in correctly with the rest of the view it's in, but in 10.9 when the animation is triggered, the table view appears fully instantly, not animating with the rest of the view.
I've created a small example and attached videos of it running below as well as the code. In the example, the view should be fading from a pure blue view to a view with an NSTableView and a red background. For the sake of simplicity, I just have the animation starting when the app launches, which is why the table view is fully visible upon start of the Mavericks run.
Video of Bug
AppDelegate.m:
#import "AppDelegate.h"
#import "SecondViewController.h"
#import "BlueBGView.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
BlueBGView *blueView = [[BlueBGView alloc]initWithFrame:self.window.frame];
SecondViewController *SVC = [[SecondViewController alloc]initWithNibName:#"SecondViewController" bundle:nil];
[self.window.contentView addSubview: blueView];
blueView.frame = ((NSView*) self.window.contentView).bounds;
[self.window.contentView addSubview: SVC.view];
//Animate
NSMutableDictionary *blueViewDict, *SVCDict;
blueViewDict = [NSMutableDictionary dictionaryWithCapacity:2];
[blueViewDict setObject:blueView forKey:NSViewAnimationTargetKey];
[blueViewDict setObject:NSViewAnimationFadeOutEffect
forKey:NSViewAnimationEffectKey];
SVCDict = [NSMutableDictionary dictionaryWithCapacity:2];
[SVCDict setObject:SVC.view forKey:NSViewAnimationTargetKey];
[SVCDict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
NSViewAnimation *animation = [[NSViewAnimation alloc]initWithViewAnimations:[NSArray arrayWithObjects:blueViewDict, SVCDict, nil]];
[animation setDuration: 5];
[animation setAnimationCurve:NSAnimationEaseIn];
[animation startAnimation];
}
#end

Related

How to lock the rotation of an AVCaptureVideoPreviewLayer?

In my endless quest to lock the rotation of a AVCaptureVideoPreviewLayer (like in the camera.app), I've decided to try VC containment.
My thought is that if I place AVCaptureVideoPreviewLayer code into a separate VC and disable its rotations, I should be able to have a locked 'view' as my background layer (augmented reality app).
But when I attempt to do this, AVCaptureVideoPreviewLayer simply fails to work at all and I get no camera video.
Below is my setup.
in ViewController.m :
#property (nonatomic, strong, readwrite) cameraViewController *cameraVC;
-(void) viewDidAppear:(BOOL)animated
{
self.cameraVC = [self.storyboard instantiateViewControllerWithIdentifier:#"cameraVC"];
[self addChildViewController:self.cameraVC];
[self.view addSubview:self.cameraVC.view];
in cameraViewController.m :
-(void) viewDidAppear:(BOOL)animated
{
NSLog(#"cameraViewController : viewDidAppear");
self.captureManager = [[CaptureSessionManager alloc] init];
[self.captureManager addVideoInput];
[self.captureManager addVideoPreviewLayer];
CGRect layerRect = self.cameraView.bounds;
[[self.captureManager previewLayer] setBounds:layerRect];
[[self.captureManager previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect), CGRectGetMidY(layerRect))];
[self.cameraView.layer addSublayer:self.captureManager.previewLayer];
[[self.captureManager captureSession] startRunning];
}
But when I set cameraViewController.m to be the root controller loaded automatically first in my storyboard, the camera video (AVCaptureVideoPreviewLayer) works fine. (minus the rotation annoyance)
Why does it work as the root VC but not as a child VC?
[I'm using XCode 4.5, iOS 6.0, and storyboards.]

MPMoviePlayerController Overlay iOS 6

Having an issue with MPMoviePLayerController with an overlay in iOS6, prior to iOS6 things were working fine.
It seems I can play a movie in full screen, before I had this code:
#interface MovieOverlayViewController : UIViewController
{
UIImageView *skiparrow;
}
#end
#implementation MovieOverlayViewController
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
touchtoskip.frame = CGRectMake( xAdjust,
yAdjust,
touchtoskip.image.size.width / scale,
touchtoskip.image.size.height / scale);
[self.view addSubview:touchtoskip];
}
Then:
overlay = [[MovieOverlayViewController alloc] init];
UIWindow *keyWnd = [[UIApplication sharedApplication] keyWindow];
[keyWnd addSubview:overlay.view];
On my MoviePlayerViewController the view DOES appear. And adds the UIViews, but I see NOTHING anymore.
Really stuck, any suggestions?
I think part of the problem is that you are getting the key window and adding a subview to that, rather than getting the window's view and adding a subview to that.
Have a look at the MoviePlayer sample, which shows how to add a player with subviews to control playback.

UIPageViewControllerSpineLocation Delegate Method Not Firing

Major head-scratcher all day on this one :-(
I have an instance of a UIPageViewController that does not appear to be firing the delegate method:
-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
I have tried various methods of displaying the UIPageViewController and have settled on a programatic approach (as opposed to a Storyboard one) that appears to be working correctly, with one exception... when rotating the iPad to landscape the spine does not appear mid-point as expected. I simply cannot find out why the delegate method does not get called.
Code Explanation (simplified for example)
Consider three classes as follows:
RootViewController - loaded when the app starts
PageViewController - loaded by RootViewController upon user initiation
PageContentViewController - loaded by PageViewController when pages are needed
Fairly self-explanatory. The RootViewController is loaded by the app upon launch. When the user taps an image within this view controller's view (think magazine cover opening a magazine) it launches the PageViewController as follows:
PageViewController *pvc = [[PageViewController alloc] initWithNibName:#"PageView"
bundle:[NSBundle mainBundle]];
pvc.view.frame = self.view.bounds;
[self.view addSubview:pvc.view];
In the actual app there is animation etc to make the transition all nice, but essentially the PageViewController's view is loaded and takes fullscreen.
PageViewController
This is the workhorse (only relevant methods shown). I have tried various examples from the infinite world of Google and written directly from the Apple docs...
#interface PageViewController : UIViewController <UIPageViewControllerDelegate, UIPageViewControllerDataSource>
#property (nonatomic, strong) UIPageViewController *pageViewController;
#property (nonatomic, strong) NSMutableArray *modelArray;
#end
#implementation TXCategoryController
-(void)viewDidLoad
{
[super viewDidLoad];
// Simple model for demo
self.modelArray = [NSMutableArray alloc] init];
for (int i=1; i<=20; i++)
[self.modelArray addObject:[NSString stringWithFormat:#"Page: %d", i]];
self.pageViewController = [[UIPageViewController alloc]
initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.delegate = self;
self.pageViewController.dataSource = self;
PageContentViewController *startupVC = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
startupVC.pageLabel = [self.modelArray objectAtIndex:0];
[self.pageViewController setViewControllers:[NSArray arrayWithObject:startupVC]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
self.pageViewController.view.frame = self.view.bounds;
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
// Relevant code to add another view...
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
// Relevant code to add another view...
}
-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
// Setting a break point in here - never gets called
if (UIInterfaceOrientationIsPortrait(orientation))
{
// Relevant code to create view...
return UIPageViewControllerSpineLocationMin;
}
// Relevant code to create 2 views for side-by-side display and
// set those views using self.pageViewController setViewControllers:
return UIPageViewControllerSpineLocationMid
}
#end
This all works perfectly well as I mentioned earlier. The PageViewController's view gets shown. I can swipe pages left and right in both portrait and landscape and the respective page number appears. However, I don't ever see two pages side-by-side in landscape view. Setting a breakpoint in the spineLocationForInterfaceOrientation delegate method never gets called.
This is such a head-scratcher I have burned out of ideas on how to debug/solve the problem. It almost behaves like the UIPageViewController isn't responding to the orientation changes of the device and therefore isn't firing off the delegate method. However, the view gets resized correctly (but that could be just the UIView autoresizing masks handling that change).
If I create a brand new project with just this code (and appropriate XIb's etc) it works perfectly fine. So something somewhere in my actual project is causing this. I have no idea where to continue looking.
As usual, any and all help would be very much appreciated.
Side Note
I wanted to add the tag 'uipageviewcontrollerspinelocation' but couldn't because it was too long and I didn't have enough reputation (1500 required). I think this is a devious ploy on Apple's part to avoid certain tags in Stackoverflow... ;-)
Finally found the problem. It was something of a red herring in its symptoms, but related just the same.
Putting a break point in the shouldAutorotateToInterfaceOrientation: method was a natural test to see if the UIViewController was even getting a rotation notification. It wasn't which led me to Apple's technical Q&A on the issue: http://developer.apple.com/library/ios/#qa/qa1688/_index.html
The most relevant point in there was:
The view controller's UIView property is embedded inside UIWindow but alongside an additional view controller.
Unfortunately, Apple, in its traditional documentation style, doesn't provide an answer, merely confirmation of the problem. But an answer on Stack Overflow yielded the next clue:
Animate change of view controllers without using navigation controller stack, subviews or modal controllers?
Although my RootViewController was loading the PageViewController, I was doing it as a subview to the main view. This meant I had two UIViewController's in which only the parent would respond to changes.
The solution to get the PageViewController to listen to the orientation changes (thus triggering the associated spine delegate method) was to remove addSubview: and instead present the view controller from RootViewController:
[self presentViewController:pac animated:YES completion:NULL];
Once that was done, the orientation changes were being picked up and the PageViewController was firing the delegate method for spine position. Only one minor detail to consider. If the view was launched in landscape, the view was still displaying portrait until rotated to portrait and back to landscape.
That was easily tweaked by editing viewDidLoad as follows:
PageContentViewController *page1 = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
NSDictionary *pageViewOptions = nil;
NSMutableArray *pagesArray = [NSMutableArray array];
if (IS_IPAD && UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
pageViewOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:UIPageViewControllerSpineLocationMid]
forKey:UIPageViewControllerOptionSpineLocationKey];
PageContentViewController *page2 = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
[pagesArray addObject:page1];
[pagesArray addObject:page2];
}
else
{
[pagesArray addObject:page1];
}
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:pageViewOptions];
self.pageViewController.delegate = self;
[self.pageViewController setViewControllers:pagesArray
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
Job done and problem solved.

Xcode storyboard switch

Someone help me understand the new storyboard in Xcode 4.2?
I know how to code to load another view controller but in the storyboard mode there are differences..
I also know there are a lot of tutorials about the navigationcontrollers, but I just want to switch UIViewControllers on storyboard.
With the normal .xib files I can switch views with this code from the RootViewController..
SecondViewController *Second = [[SecondViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:Second animated:YES];
When I use it in the storyboard mode it just loads the UIAlertView on the SecondViewController.m and the screen appears to be black?
Any help would be appreciated, also attached the Xcode project...
Here is the zip..
-x- Jay Ruben
you can do this:
SecondViewController *second= [self.storyboard instantiateViewControllerWithIdentifier:#"second"];
[self presentModalViewController:second animated:YES];
Don't forget to give the second view controller an identifier like "second".
Otherwise you can connect both view controllers with a segue. Hold CTRL an drag from the first to the second view Controller. Now you can choose "push" and give the segue a name to switch the View programmatically like this:
[self performSegueWithIdentifier:#"second" sender:self];
Push Segues will only work if a navigation controller is set.
You can also switch this way:
// get the view that's currently showing
UIView *currentView = self.view;
// get the the underlying UIWindow, or the view containing the current view
UIView *theWindow = [currentView superview];
UIView *newView = aTwoViewController.view;
// remove the current view and replace with myView1
[currentView removeFromSuperview];
[theWindow addSubview:newView];
// set up an animation for the transition between the views
CATransition *animation = [CATransition animation];
[animation setDuration:0.5];
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromRight];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[[theWindow layer] addAnimation:animation forKey:#"SwitchToView2"];
Download the sample project here.

Adding splashscreen to iphone app in AppDelegate

I'm running xcode-4.2 and the project is based for ios5 using storyboards.
I've created a single view application , using the template provided by Apple.
In the storyboard I the removed the viewcontroller created for me and added a UITabBarController.
Next I added a new class MyTabBarController which is a subclass of UITabBarController.
Now I want to show a splashscreen before the TabBar appears. So I can do some loading and calculation in the background.
I thought AppDelegate.m would be a good place for this. Since that's the place where my rootview get's loaded not ? Or should a show the splashscreen from the rootviewcontroller which is MyTabBarController in my case ?
So I created a xib file. I'm surprised you can add .xib files to ios5 storyboard projects. The xib file is called SplashView.xib it has a single view with an image on it.
Code in AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
_splashScreen = [[UIViewController alloc] initWithNibName:#"SplashView" bundle:nil];
//_splashScreen is defined as:#property (strong, nonatomic) UIViewController *splashScreen;
[_window.rootViewController presentModalViewController:_splashScreen animated:NO];
[self performSelector:#selector(hideSplash) withObject:nil afterDelay:2];
return YES;
}
The problem is nothing happens. Even if I change the value from 2 to 200. The application starts up as if there is no splashscreen.
As you might have noticed I'm still struggling with the design of objective-c and iphone application. I hope a decent answer to my question will bring some clarity to the subject.
Thanks in advance!
Splash screens are built into iOS apps. All you need to do is create a file called Default.png and Default#2x.png (for retina displays) and it will work as a splash screen for when the app launches.
You can also set what these images will be in your apps info.plist.
I've dealt with a few clients who wanted to use an animated splash.
Though I'm totally against this, following Apple's HIG,
those clients just don't understand...
Anyway, since - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions; has to return boolean,
it's very important not to halt it for anything.
Also, since launch time is measured by iOS, if it's taking too long, the app will be terminated by iOS!
For this reason, I often use - (void)applicationDidBecomeActive:(UIApplication *)application;
with some kind of flag to indicate if it happened at launch or at returning from background mode.
Or, you should use a NSTimer or - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
so, didFinishLaunchingWithOptions can return without being blocked for processing your animated splash.
This delayed performSelector should be implemented not only for hiding action (like the way you intended it), but also for starting the animation.
If you are using storyboard, you can just add the splash UIImageView to your window.rootViewController.view like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIImage *splashImage = [UIImage autoAdjustImageNamed:#"Default.png"];
UIImageView *splashImageView = [[UIImageView alloc] initWithImage:splashImage];
[self.window.rootViewController.view addSubview:splashImageView];
[self.window.rootViewController.view bringSubviewToFront:splashImageView];
[UIView animateWithDuration:1.5f
delay:2.0f
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
splashImageView.alpha = .0f;
CGFloat x = -60.0f;
CGFloat y = -120.0f;
splashImageView.frame = CGRectMake(x,
y,
splashImageView.frame.size.width-2*x,
splashImageView.frame.size.height-2*y);
} completion:^(BOOL finished){
if (finished) {
[splashImageView removeFromSuperview];
}
}];
return YES;
}
I think the reason why directly just add the UIImageView to window is because iOS will bring the rootViewController.view to front when the default splash will hide. And this will overlap the animation. This means the animation does happen but it's behind the rootViewController.
I just add an identical image to the launch image to my first view controller and then fade it (or whatever animation you require) - this avoids pausing the app load in the AppDelegate.
You need to ensure that the image has the same size and origin as your launch image e.g. to set the image to display on my first view controller which is a tableViewController:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.tableView.bounds];
imageView.image = [UIImage imageNamed:#"[imagename]"];
[self.tableView addSubview:imageView];
[self.tableView bringSubviewToFront:imageView];
// Fade the image
[self fadeView:imageView];
-(void)fadeView:(UIView*)viewToFade
{
[UIView animateWithDuration:FADE_DURATION
animations:^ {
viewToFade.alpha = 0.0;
}
];
}