Quartz Composer Screensaver in Xcode - xcode

My aim is to bundle a Quartz Composer file into Xcode and build a .saver file. I am currently using the Xcode pre-made template but having problems getting the screensaver to work. I am importing the .qtz file into the project and using QCView to render it on screen, however when I test the built .saver file all I see is a black screen.
- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
self = [super initWithFrame:frame isPreview:isPreview];
if (self) {
[self setAnimationTimeInterval:1/30.0];
NSRect viewBounds = [self bounds];
//create the quartz composition view
qcView = [[QCView alloc] initWithFrame: NSMakeRect(0, 0, viewBounds.size.width, viewBounds.size.height)];
//make sure it resizes with the screensaver view
[qcView setAutoresizingMask:(NSViewWidthSizable|NSViewHeightSizable)];
//match its frame rate to your screensaver
[qcView setMaxRenderingFrameRate:30.0f];
//get the location of the quartz composition from the bundle
NSString* compositionPath = [[NSBundle mainBundle] pathForResource:#"QuartzComposerFileName" ofType:#"qtz"];
//load the composition
[qcView loadCompositionFromFile:compositionPath];
//add the quartz composition view
[self addSubview:qcView];
}
return self;
}

I have tried doing the same thing using Xcode 6.4 and it appears that QCView is not working with Xcode 6.4. I put an image in the view and set the size of my QCView to be smaller than the frame. I see a black box for the subview but it never renders. This is true for even the most basic QC composition.
You might be able to get it to work by rendering the composition yourself using a QCRenderer. I gave up before trying this, but here is the documentation: https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/QuartzFramework/Classes/QCRenderer_Class/

Related

SceneKit animation doesn't play in iOS

I have an example of animations fully working for OS X, start, pause, change speed etc.
Now I started porting this to iOS and can't even start the animation. I simplified the code to a minimum - it works in OS X, but not on iOS.
What I do is
Show a scene with a (animated) character in it (idle animation) - works both in OS X and iOS
Start a run animation on the character. This works on OS X, run animation starts and loops. On iOS the character is positioned at the start of run animation. But it doesn't run...
If I start with e.g. run scene instead of idle, it works - character runs. The problem is when I start an animation (any) after the scene is loaded. It loads the model with the animation but doesn't play.
After detailed comparison between the OS X and iOS version I found 2 differences, which probably are related but I can't figure out how fix them:
In the OS X version the character is not animated until I start an animation. In the iOS version when I attach the nodes from the idle (or whatever other) scene to the root, it's animated. I don't know how to change this.
The OS X version has the scene.dae attached to the Scene View in the Storyboard - this is also the case in iOS. But in iOS for some reason this attachment is not working, ´self.scene´ is nil. This is why I have to instantiate and assign the scene programmatically. I couldn't fix it, tried re-adding the Scene View, assigning the outlet etc.
The scene kit view is added using storyboard. idle and run are .dae files. Each of them contains a full model with the character, and the animation. I just double checked that the animation identifiers are the same as in the .dae files. The models are actually provided in an example from Apple and work perfectly on OS X...
This is the code:
View controller:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated {
[self.sceneView loadScene];
}
#end
Scene kit view header:
// ASCView.h
#import <UIKit/UIKit.h>
#import <SceneKit/SceneKit.h>
#interface ASCView : SCNView
- (void)loadScene;
#end
Scene kit view implementation:
//
// ASCView.m
// anim_test
//
//
#import "ASCView.h"
#implementation ASCView
- (void)loadScene {
self.backgroundColor = [UIColor blueColor];
self.allowsCameraControl = YES;
[self loadSceneAndAnimations];
}
#pragma mark - Animation loading
- (void)loadSceneAndAnimations {
// Load the character from one of our dae documents, for instance "idle.dae"
NSURL *idleURL = [[NSBundle mainBundle] URLForResource:#"idle" withExtension:#"dae"];
SCNScene *idleScene = [SCNScene sceneWithURL:idleURL options:nil error:nil];
SCNScene *scene = [SCNScene sceneNamed:#"scene.dae"];
self.scene = scene;
NSLog(#"scene: %#", self.scene);
// Merge the loaded scene into our main scene in order to
// place the character in our own scene
for (SCNNode *child in idleScene.rootNode.childNodes)
[self.scene.rootNode addChildNode:child];
// Load and start run animation
// The animation identifier can be found in the Node Properties inspector of the Scene Kit editor integrated into Xcode
[self loadAndStartAnimation:#"run" withIdentifier:#"RunID"];
}
- (void)loadAndStartAnimation:(NSString *)sceneName withIdentifier:(NSString *)animationIdentifier {
NSURL *sceneURL = [[NSBundle mainBundle] URLForResource:sceneName withExtension:#"dae"];
SCNSceneSource *sceneSource = [SCNSceneSource sceneSourceWithURL:sceneURL options:nil];
CAAnimation *animationObject = [sceneSource entryWithIdentifier:animationIdentifier withClass:[CAAnimation class]];
NSLog(#"duration: %f", [animationObject duration]); //0.9
animationObject.duration = 1.0;
animationObject.repeatCount = INFINITY;
[self.scene.rootNode addAnimation:animationObject forKey:#"foofoofoo"];
NSLog(#"animation: %#",[self.scene.rootNode animationForKey: #"foofoofoo"]);
NSLog(#"is paused: %#",[self.scene.rootNode isAnimationForKeyPaused: #"foofoofoo"] ? #"yes" : #"no"); //NO
}
#end
Ohh I found it. In iOS it seems I have to pass the options, in this case:
#{SCNSceneSourceAnimationImportPolicyKey:SCNSceneSourceAnimationImportPolicyPlayRepeatedly}
In OS X I don't - probably the defaults are different.

Opengl es high resolution iphone 4

I created an empty iOS project and then added a custom GLView class which is then added to AppDelegate. I have following questions:
1) How do I enable hi-res retina mode on iPhone 4? Currently I am using the following code to check for device:
CGRect screenBounds = [[UIScreen mainScreen] bounds];
self.window = [[[UIWindow alloc] initWithFrame:screenBounds] autorelease];
// Override point for customization after application launch.
_view = [[GLView alloc] initWithFrame:screenBounds];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
NSLog(#"iPad detected");
}
else {
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)] && [[UIScreen mainScreen] scale] == 2) {
NSLog(#"iPhone4 detected");
_view.contentScaleFactor = [[UIScreen mainScreen] scale];
}
else {
NSLog(#"iPhone detected");
}
}
self.window.backgroundColor = [UIColor whiteColor];
//self.window.rootViewController = [[[UIViewController alloc] initWithNibName:nil bundle:nil] autorelease];
[self.window addSubview:_view];
But even after setting content factor it is drawing pretty poor quality polygons with jagged edges as shown in the image below:
http://farm8.staticflickr.com/7358/8725549609_e2ed1e0e2a_b.jpg
Is there any way to set the resolution to 960x640 instead of the default 480x320 ?
Please note that I can not use "someImage#2x.png" because I am generating images at runtime in the render buffer.
2) Second problem I am having is this warning message:
"Application windows are expected to have a root view controller at the end of application launch"
Thank you for your time.
As for the first question I do not know the pipeline of GLView initializer but content scale must be set before the render buffer is made (usually before renderbufferStorage:: method). To see if dimensions of the buffer are correct (should be 960x640) use function:
GLint width;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width)
Even if the buffer is retina and dimensions are correct those polygons might still be jagged if you do not use any sort of anti-alias. The easiest way to make an antialiased GL view in iOS is probably multisampling, try searching for glResolveMultisampleFramebufferAPPLE() (you will need a few more lines beside this one though).

How to develop for multiple iOS devices, i.e. multiple storyboards?

I am currently developing an app for the iPhone 3GS. The deployment target is set to 5.1 and I have created a rich storyboard with lots of segues and scenes. Last night I had the idea that I wanted to make the app available for the iPad, iPhone 4, and iPhone 5. I decided to create a separate storyboard for the different screen sizes / resolutions. Now I am not sure if this is the best practice, as I have just recently started reading about springs and struts on SO, so I don't know much information about it, but for my sake, I just wanted to launch a different storyboard when the application finished launching. However this desired effect is not happening.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// ViewControllerWelcome *viewControllerWelcome = (ViewControllerWelcome *)[[ViewControllerWelcome alloc]init];
// NSManagedObjectContext *context = (NSManagedObjectContext *) [self managedObjectContext];
// if (!context) {
// NSLog(#"\nCould not create *context for self");
// }
//[viewControllerWelcome setManagedObjectContext:context];
// Do I need to declare my view controllers here?
// Pass the managed object context to the view controller.
CGSize iOSDeviceScreenSize = [[UIScreen mainScreen] bounds].size;
if (iOSDeviceScreenSize.height == 480)
{
// Instantiate a new storyboard object using the storyboard file named iPhoneLegacy
UIStoryboard *iPhoneLegacy = [UIStoryboard storyboardWithName:#"iPhoneLegacy" bundle:nil];
// Instantiate the initial view controller object from the storyboard
UIViewController *ViewControllerWelcome = [iPhoneLegacy instantiateInitialViewController];
// Instantiate a UIWindow object and initialize it with the screen size of the iOS device
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Set the initial view controller to be the root view controller of the window object
self.window.rootViewController = ViewControllerWelcome;
// set the window object to be the key window and show it
[self.window makeKeyAndVisible];
}
if (iOSDeviceScreenSize.height == 968)
{
// Instantiate a new storyboard object using the storyboard file named iPhone4
UIStoryboard *iPhone4 = [UIStoryboard storyboardWithName:#"iPhone4" bundle:nil];
UIViewController *ViewControllerWelcome = [iPhone4 instantiateInitialViewController];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = ViewControllerWelcome;
[self.window makeKeyAndVisible];
}
// iPhone 5 1136 x 640
// iPad Legacy 1024 x 768
return YES;
}
When I try testing to see if the different storyboard file are loading in the Simulator, Simulator just loads the iPhoneLegacy storyboard.
Does this code only work for the physical devices, and do I need separate code for the Simulator?
Fist of all, DELETE YOUR EXTRA STORYBOARDS! You only need one for the iPhone and one for the iPad.
There is a simple way to make a single storyboard for all iPhone/iPod Touch sizes.
Keep only ONE storyboard for the iPhone screen size (including iPhone 5).
Make a #2x file for all of your images.
To switch between 3.5 and 4 inch size, Apple provides a button in the bottom right that looks like a rectangle with arrows pointing in or out - that button will switch between the 3.5 and 4 inch screen sizes.
That's it! No code is actually needed to make a single storyboard for each iPhone/iPod Touch.
For the iPad, you are going to need to create a new storyboard that is made for the iPad and you are going to need to update your UI code to make sure it's compatible with both iPhone and iPad screen sizes. Again, make sure to make #2x image sizes for the iPad as well.

Two Finger Drag with IKImageView and NSScrollView in Mountain Lion

I have a Mac App that's been in the app store for a year or so now. It was first published with target SDK 10.7, Lion. Upon the update to Mountain Lion it no longer works.
The application displays large images in an IKImageView which is embedded in an NSScrollView. The purpose of putting it into a scrollview was to get two finger dragging working, rather than the user having to click to drag. Using ScrollViewWorkaround by Nicholas Riley, I was able to use two finger scrolling to show the clipped content after the user had zoomed in. Just like you see in the Preview app.
Nicholas Riley's Solution:
IKImageView and scroll bars
Now in Mountain Lion this doesn't work. After zooming in, pinch or zoom button, the image is locked in the lower left portion of the image. It won't scroll.
So the question is, what's the appropriate way to display a large image in IKImageView and have two finger dragging of the zoomed image?
Thank you,
Stateful
Well, Nicholas Riley's Solution is an ugly hack in that it addresses the wrong class; the issue isn't with NSClipView (which he subclassed, but which works just fine as is), but with IKImageView.
The issue with IKImageView is actually quite simple (God knows why Apple hasn't fixed this in what? … 7 years ...): Its size does not adjust to the size of the image it displays. Now, when you embed an IKImageView in an NSScrollView, the scroll view obviously can only adjust its scroll bars relative to the size of the embedded IKImageView, not to the image it contains. And since the size of the IKImageView always stays the same, the scroll bars won't work as expected.
The following code subclasses IKImageView and fixes this behavior. Alas, it won't fix the fact that IKImageView is crash-prone in Mountain Lion as soon as you zoom …
///////////////////// HEADER FILE - FixedIKImageView.h
#import <Quartz/Quartz.h>
#interface FixedIKImageView : IKImageView
#end
///////////////////// IMPLEMENTATION FILE - FixedIKImageView.m
#import "FixedIKImageView.h"
#implementation FixedIKImageView
- (void)awakeFromNib
{
[self setTranslatesAutoresizingMaskIntoConstraints:NO]; // compatibility with Auto Layout; without this, there could be Auto Layout error messages when we are resized (delete this line if your app does not use Auto Layout)
}
// FixedIKImageView must *only* be used embedded within an NSScrollView. This means that setFrame: should never be called explicitly from outside the scroll view. Instead, this method is overwritten here to provide the correct behavior within a scroll view. The new implementation ignores the frameRect parameter.
- (void)setFrame:(NSRect)frameRect
{
NSSize imageSize = [self imageSize];
CGFloat zoomFactor = [self zoomFactor];
NSSize clipViewSize = [[self superview] frame].size;
// The content of our scroll view (which is ourselves) should stay at least as large as the scroll clip view, so we make ourselves as large as the clip view in case our (zoomed) image is smaller. However, if our image is larger than the clip view, we make ourselves as large as the image, to make the scrollbars appear and scale appropriately.
CGFloat newWidth = (imageSize.width * zoomFactor < clipViewSize.width)? clipViewSize.width : imageSize.width * zoomFactor;
CGFloat newHeight = (imageSize.height * zoomFactor < clipViewSize.height)? clipViewSize.height : imageSize.height * zoomFactor;
[super setFrame:NSMakeRect(0, 0, newWidth - 2, newHeight - 2)]; // actually, the clip view is 1 pixel larger than the content view on each side, so we must take that into account
}
//// We forward size affecting messages to our superclass, but add [self setFrame:NSZeroRect] to update the scroll bars. We also add [self setAutoresizes:NO]. Since IKImageView, instead of using [self setAutoresizes:NO], seems to set the autoresizes instance variable to NO directly, the scrollers would not be activated again without invoking [self setAutoresizes:NO] ourselves when these methods are invoked.
- (void)setZoomFactor:(CGFloat)zoomFactor
{
[super setZoomFactor:zoomFactor];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToRect:(NSRect)rect
{
[super zoomImageToRect:rect];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomIn:(id)sender
{
[super zoomIn:self];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomOut:(id)sender
{
[super zoomOut:self];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToActualSize:(id)sender
{
[super zoomImageToActualSize:sender];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToFit:(id)sender
{
[self setAutoresizes:YES]; // instead of invoking super's zoomImageToFit: method, which has problems of its own, we invoke setAutoresizes:YES, which does the same thing, but also makes sure the image stays zoomed to fit even if the scroll view is resized, which is the most intuitive behavior, anyway. Since there are no scroll bars in autoresize mode, we need not add [self setFrame:NSZeroRect].
}
- (void)setAutoresizes:(BOOL)autoresizes // As long as we autoresize, make sure that no scrollers flicker up occasionally during live update.
{
[self setHasHorizontalScroller:!autoresizes];
[self setHasVerticalScroller:!autoresizes];
[super setAutoresizes:autoresizes];
}
#end

iPad: How to display a different screen depending on orientation (landscape / portrait)

I have an iPad application that can be used in all four view modes (portrait up/down and landscape left/right). But at a certain point I have a View that I only want to be seen in landscape mode. So I do the following in the UIViewController that will trigger the action to view the landscape-only view:
- (void) showProperty:(Property *) property {
if ([self interfaceOrientation] == UIInterfaceOrientationLandscapeLeft || [self interfaceOrientation] == UIInterfaceOrientationLandscapeRight) {
PropertyViewController *propertyView = [[PropertyViewController alloc] initWithNibName:#"PropertyViewController" bundle:[NSBundle mainBundle]];
propertyView.property = property;
[self.navigationController pushViewController:propertyView animated:YES];
[propertyView release];
propertyView = nil;
}
else {
RotateDeviceViewController *rotateView = [[RotateDeviceViewController alloc] initWithNibName:#"TabRotate" bundle: [NSBundle mainBundle]];
rotateView.property = property;
[self.navigationController pushViewController:rotateView animated:YES];
[rotateView release];
rotateView = nil;
}
}
This works fine and thus shows either the desired screen (PropertyViewController) when the iPad is held in landscape mode, and if not it shows the RotateDeviceViewController which shows the user a message that he/she is supposed to rotate the device to correctly view the screen.
So when the user then rotates his/her device to landscape mode I want to show them the right view (PropertyViewController). And all of this kinda works!
The problem arises though in this RotateDeviceViewController.. There I have the following:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
[self showProperty];
return UIInterfaceOrientationIsPortrait(interfaceOrientation);
}
- (void) showProperty {
PropertyViewController *propertyView = [[PropertyViewController alloc] initWithNibName:#"PropertyViewController" bundle:[NSBundle mainBundle]];
propertyView.property = property;
[self.navigationController pushViewController:propertyView animated:YES];
[propertyView release];
}
So as soon as I rotate the device (when viewing the RotateDeviceViewController) to landscape mode I show the user the PropertyViewController. This works... But when the PropertyViewController appears it shows my layout 90 degrees rotated. So basically it shows the content in portrait mode instead of using the landscape mode (which is actually the way you are holding the device)..
I hope this makes sense and someone can show me what's causing this.
Screenshots to make it more clear:
When device is held in portrait mode
After rotating the device
At this point
- (BOOL)shouldAutorotateToInterfaceOrientation:UIInterfaceOrientation)interfaceOrientation
You are telling the view controller what orientations you support. The device has not actually rotated yet therefore the view controllers intefaceOrientation property will still be portrait so when it is pushed onto the stack it thinks the device is portrait.
pseudo code
shouldAutoRotate... // at this point self.interfaceOrientation == portrait
// you push your controller here so it loads when the property is
I'm not sure if this will work well but the earliest I can see you can push is in
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation

Resources