Thursday, 28 August 2014

XCode 5 Quartz Composer Bug Fix

Apple's XCode 5 has a bug that breaks it's ability to embed a Quartz Composer composition within an application. The post shows how to fix the bug and continue to use Quartz Composer in XCode 5.

XCode is the program provided by Apple for software development for Mac computers and iPhones. It is available free of charge from the Apple App Store and is an incredible piece of software. Apple released XCode 5 in September 2013 together with some bugs.

Quartz Composer is a node-based visual programming language provided as part of XCode and is for processing and rendering graphical data.

Quartz Composer allows you to do some very cool animations easily - so code wrangling doesn't get in the way of your creativity. Embedding a Quartz Composer Composition in an XCode App wraps it in functionality that can grab data from the internet and use it as content within the animation. In my case I built a tool to search, moderate and creatively display tweets.


In previous versions of XCode the Interface Builder tool allowed you to graphically create a Quartz Composer canvas within the application. The bug stops this working at all - the xib file will not load if it uses the all important QCView or QCParameterView objects. The workaround is to create the view and attach the QC Composition to it programmatically rather than via Interface Builder in the xib.

Here is my the code omitting lots of lines not relevant to this post:

DisplayController.h
#import <Quartz/Quartz.h>
@interface DisplayController : NSObject
{
  __strong QCView * qcView;
  QCCompositionParameterView *qcParamsView;
}

@property (unsafe_unretained) IBOutlet NSWindow *displayWindow;
@property (unsafe_unretained) IBOutlet NSWindow *displaySettings;
@property (strong) IBOutlet QCCompositionParamterView *paramsView;
@end

DisplayController.m
@synthesize qcView;
@synthesize qcParamsView;

- (void) awakeFromNib
{
  NSString *path = [[NSBundle mainBundle] pathForResource:@"Luna Vertical2014" ofType:@"qtz"];
  NSView *superView = [self.displayWindow contentView];
  qcView = [[QCView alloc] initWithFrame:superView.frame];
  [superView addSubview:qcView];
  [superView setTranslatesAutoresizingMaskIntoConstraints:YES];
  [superView setAutoresizesSubviews:YES];
  [qcView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
  
  [superView addConstraint:
   [NSLayoutConstraint constraintWithItem: qcView
                                attribute: NSLayoutAttributeWidth
                                relatedBy: NSLayoutRelationEqual
                                   toItem: superView
                                attribute: NSLayoutAttributeWidth
                               multiplier: 1
                                 constant: 0]];
    
  [superView addConstraint:
   [NSLayoutConstraint constraintWithItem: qcView
                                attribute: NSLayoutAttributeHeight
                                relatedBy: NSLayoutRelationEqual
                                   toItem: superView
                                attribute: NSLayoutAttributeHeight
                               multiplier: 1
                                 constant: 0]];
  [superView addConstraint:
   [NSLayoutConstraint constraintWithItem: qcView
                                attribute: NSLayoutAttributeCenterX
                                relatedBy: NSLayoutRelationEqual
                                   toItem: superView
                                attribute: NSLayoutAttributeCenterX
                               multiplier: 1
                                 constant: 0]];
  [superView addConstraint:
   [NSLayoutConstraint constraintWithItem: qcView
                                attribute: NSLayoutAttributeCenterY
                                relatedBy: NSLayoutRelationEqual
                                   toItem: superView
                                attribute: NSLayoutAttributeCenterY
                               multiplier: 1
                                 constant: 0]];

  [qcView unloadComposition];
  [qcView loadCompositionFromFile:path];
  [qcView setMaxRenderingFrameRate: 30.0];
  [qcView startRendering];
  
  if(![qcView loadCompositionFromFile:path])
  {
      NSLog(@"QC Composition failed to load");
      [NSApp terminate:nil];
  }
  NSLog(@"QC Composition has been loaded!!!!");
  NSLog(@"inputKeys: %@", qcView.inputKeys);
  
  //Create a parameters view
    //Note that a new referencing outlet was added from
    //Display Settings window to DisplayController
    //by dragging the round circle on the far left over 
    //to the blue cube in the xib.
    //Check out displaycontroller.h and .m
    
  NSView *paramsSuperView = [self.displaySettings contentView];
  qcParamsView = [[QCCompositionParameterView alloc] initWithFrame:paramsSuperView.frame];
  [paramsSuperView addSubview:qcParamsView];
  [paramsSuperView setTranslatesAutoresizingMaskIntoConstraints:YES];
  [paramsSuperView setAutoresizesSubviews:YES];
  [qcParamsView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
  [qcParamsView setCompositionRenderer:qcView];
  
    // If you need mouse/keyboard interaction with QC uncomment this line
    //    qcView.eventForwardingMask = NSAnyEventMask;
}