GLPaint Sample: how to create the view programmatically? - iphone

I need to draw something on an image, like in the GLPaint sample from Apple, but I need to create the EAGL surface where render the OpenGL programmatically.
In the sample is instatiated in the MainWindow.xib
If I try to create the view programmatically with something like:
self.drawingView = [[PaintingView alloc] initWithCoder:nil];
drawingView.frame = CGRectMake(0, 0, 320, 380);
[self.view addSubview:drawingView];
I got this error:
failed to make complete framebuffer object 8cd6
Maybe something related to the init? Any hints? Thanks.

From a quick glance inside the source, Apple have put all of the significant initialisation stuff for PaintingView inside initWithCoder:, from lines 77 to 168. That's part of the NSCoding protocol, which is used for archiving and unarchiving objects from files. If you call initWithCoder:nil, quite probably the UIView implementation of initWithCoder isn't able to get some relevant value that it needs. At a guess, it probably starts being size zero, which isn't a valid size for a framebuffer object.
I'd suggest you replace initWithCoder:(NSCoder *)coder with initWithFrame:(CGRect)frame, calling the same on super at line 86. For an even better implementation, implement both initWithFrame: and initWithCoder:, having the two call a common section for everything enclosed in the if((self = [super initWithXXX:argument])) block.

Related

How to approach a common xib or controls across multiple view controllers?

I have an animated character coexisting with other conventional controls in an xib.
All works well. No issues there. The animation is done through UIImageView image flipping. An animation engine (state machine) triggered by a timer runs the show. The character uses six UIImageViews to render the different portions of the character that need to be animated.
Now I am looking at the possibility of the app getting more complex by adding a few more UIViewControllers (and their xib's). As you may have guessed, my character would have to exist in any xib that slides on top of the prior one.
What might be the best approach to implementing this? I suppose I could copy-paste the UIImageView's and instantiate a new animation engine (or pass a pointer) to each new xib.
I many ways what I need is for a new xib to only cover 2/3 of the prior xib and that would do it, but I am not sure that this works. Not sure about events and how they'd work with two overlapping xib's (if it's even possible).
While I experiment I thought I'd ask and see if anyone who has been here before might have an interesting approach or two to share.
Your question seems to be about ownership and not about animation. Try isolating the character into its own xib, (say Character.xib) and creating a CharacterLoader class. The CharacterLoader class would have a property of character and look something like:
CharacterLoader.h
#interface CharacterLoader : NSObject {
}
#property(nonatomic, retain) IBOutlet Character *character;
+ (Character *)loadCharacter;
#end
CharacterLoader.m
...
+ (Character *)loadCharacter {
CharacterLoader *loader = [[CharacterLoader alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"Character" owner:loader options:nil];
Character *character = [loader.character autorelease];
[loader release];
return character;
}
...
If you find yourself making a lot of these loader classes, you can roll them into a single class whose role is to basically load nibs programmatically like this. Another alternative would be to just create the Character and all other shared content programmatically, but that's probably not desirable for you since you intentionally are approaching it by using nibs.
In the interest of closing the loop, this is what I ended-up doing:
I created a UIViewController subclass with a nib file. Called it "AnimatedCharacter".
"AnimatedCharacter.xib" consists of all of the elements required to create the character. It has a range of controls connected to IBOutlets and IBActions driven from "AnimatedCharacter.m". In my case "AnimatedCharacter.m" creates an NSTimer that fires-off a state machine at regular intervals to decide what to do with the character. It also implements audio playback through standard means.
Other than that there's nothing special about these files/code. In other words, I did not do anything out of the ordinary to prepare them for insertion into another UIViewController.
In the main view controller .h file:
#import "AnimatedCharacter.h"
...
AnimatedCharacter *character;
...
#property (nonatomic, retain) AnimatedCharacter *character;
Then, in the main view controller's .m file:
#synthesize character;
...
- (void)viewDidLoad
{
character = [[AnimatedCharacter alloc] init];
character.view.frame = CGRectMake(54.0, 0.0, 150.0, 150.0);
[self.view addSubview:character.view];
...
With that in place I can now do things like:
-(void)FadeOut:(SEL)selector
{
[UIView beginAnimations:#"resize" context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:selector];
character.view.frame = CGRectMake(0.0, 0.0, 50.0, 50.0);
[UIView commitAnimations];
}
And, generally control the character as I see fit through the various member functions in "AnimatedCharacter.m". This is effectively working as a nib within a nib. Not separate threads, but both nibs are discharging their duties as expected, receiving inputs and running their respective shows.
I'd be interested in comments as to the good/bad/ugly aspects of what I've chosen to do.

Exactly what should happen in a CALayer's display/drawRect methods?

Which, if either, of these methods would be an appropriate place to:
Change the text of a CATextLayer
Load a different image into a CAImageLayer
Tell sublayers to update themselves
Dude I may be way drunk ... but there is NO drawRect method in CAlayers
I think you can use drawInContext: to actually (gulp) draw in to CALayers, but nobody is man enough to do that since WW2.
Regarding display, you don't need to call it, it basically updates what you set using .contents.
I just use .contents something like this ...
[self.view setLayer:rearLayer];
[self.view setWantsLayer:YES];
rearLayer.frame = CGRectMake(gameBlah,gameBlah, 1024,768);
// note that we are dealing there with the mysteries of rearLayer positioning;
// it is measured by the SUPER layer of the layer in question!
// (indeed much as frame for the view is, if you think about it ..)
rearLayer.contents = (id)loadMacStylePng(#"spaceShip");
Say one had the guts to write one's own drawInContext: ...
In that case, it gets called (or abstracted out ... or recalculated, or something) when you call displayAsNeeded. (I've never needed to call displayAsNeeded:, that's for sure.)

Replacing the content of UIImage(s) loaded from XIB at runtime

For a concept I'm developing, I need to load XIB files manually and by using class and instance method swizzling I have been able to intercept calls to imageCustomNamed, imageCustomWithContentsOfFile and imageCustomWithCGImage for the UIImage class and initCustomWithImage for UIImageView. What I want to to is detect the image name and replace it with some content rendered on the fly in place of the one set in IB at design time.
for example:
when XIB has an UIImageView object whose Image property is set to "About.png", I need to intercept the loading of that image and replace with another one depending on certain condition. It would be ok even to replace the image after the UIImageView object has loaded the image set at design time, but looks like the original name of the UIImage used to set the content of UIImageView is not stored anywhere.
I cannot use IBOutlets as I don't know the content of XIB file before hand; this is a class that should work on any XIB not just a particular one.
The custom methods are in fact being called in placed of the default ones, but looks like that when loading XIb the system uses imageCustomWithCGImage which accept a CGImageRef as argument; from it there is no way to know the origin (i.e: the image file name)
Any idea on how I can intercept the loading of images?
In OS 3, at least, you can override UIImageNibPlaceholder's initWithCoder:. Replace it with something like this:
-(id)hack_UIImageNibPlaceholder_initWithCoder:(NSCoder*)coder
{
NSString * name = [coder decodeObjectForKey:#"UIResourceName"];
[self release];
return [[UIImage imageNamed:name] retain];
}
I'm not sure what happens if you load nibs from other bundles (e.g. frameworks).
Perhaps you can set another property of UIImageView in xib file, and then reference that property? For example label, or tag...
It's not a good idea to swizzle a private class(UIImageNibPlaceholder)'s method.
But you can overload its superclass(UIImage)'s method. Seems like UIImageNibPlaceholder did't implement 'awakeAfterUsingCoder', so you can safely do this until Apple did it one day.
#import "UIImage+xib.h"
#implementation UIImage (xib)
- (id)awakeAfterUsingCoder:(NSCoder *)coder{
NSString* imageName = [coder decodeObjectForKey:#"UIResourceName"];
if(imageName){
return [UIImage imageNamed:imageName];
}
return self;
}
#end

UITableViewCell doesn't clear context before drawing

I have a subclass of UITableViewCell which contains several elements - UIImageViews, Labels, etc.
Since this cell is intended to be reusable, I want to be able to change it's appearance a bit depending on what data it is currently displaying.
So as an example - I have this view in my custom UITableViewCell:
UIImageView* delimeterView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"cellDelimiter.png"]];
Which I want to be able to hide sometimes like this:
- (void) setRecord:(id)record__ {
if (record__.type == NO_DELIMETER_VIEW)
delimeterView.hidden = YES;
else
delimeterView.hidden = NO;
[self setNeedsLayout];
}
But the problem is that delimeterView will always be displayed on the cell, just like if it was drawn once in the init method and then drawing context was never changed or cleared. I've tried setting clearsContextBeforeDrawing property to YES for both cell and its contentView, I've also tried setting opaque for cell and its contentView to NO since I've read there might be some problems with that aswell in case you're using transparent views.
Nothing helps.
It looks like UITableViewCell never clears its graphic context and just paints over old elements.
Any tips on what am I doing wrong?
I know I can probably fix this by doing custom drawing but I'd rather not.
First, are you sure that delimeterView in setRecord: is actually pointing to your delimeterView? In the code example you give, you assign it to a local. Do you later assign this to an ivar? (You should always use accessors to access ivars: self.delimeterView).
Next, calling -setNeedsLayout just schedules a call to -layoutIfNeeded, which walks the hierarchy calling -layoutSubviews. The default implementation of -layoutSubviews does nothing. You probably meant to call -setNeedsDisplay here, or you need to implement -layoutSubviews to do what you want.

When do I alloc and init objects in iPhone programming with Objective-C?

Sometimes when I program for the iPhone, I wonder when you have to allocate and initialize objects and when not to. When you are using UI controls, it seems as if you don't have to do so. Is this true and why?
(Assume that they have been declared in the .h of the view controller.)
Example:
label1.text = #"Hello";
vs
label1 = [[UILabel alloc] init];
label1.text = #"Hello";
Is this because I'm using Interface Builder? Would I have to do this if I were to write our my GUI in code?
Your confusion is because of the NIB file - a NIB file is basically a frozen object graph (i.e. a object with children, who has other children, etc). When you load that NIB file, the runtime calls all of the allocs and inits for you, so that they're already created.
When you want to create an object that hasn't been previously specified in the NIB file, that's when you need alloc/init.
You basically need to alloc/init ALL objects except for static strings, as above. Even when you're using convenience methods, such as +[NSString stringWithFormat:...], behind the scenes an alloc and init is still occurring. These convenience methods usually just do the alloc and init, and then toss in an -autorelease as well so that you don't have to worry about cleaning up.
If you're just creating a temporary object, and there's a convenience method that fits, use it. If you want your object to stay around and there's convenience method, usually it's fine to call it and add a -retain, or just use alloc/init.
Obviously, if there's no convenience method, use alloc/init.