Subclassing UIView or composing with UIView - iphone

I'll start by saying what I want to do because I'm unsure if I'm asking the right question. I'm making a grid based map and am going to hold an array of objects to keep the state and presentation of the map. Each object will be of a Tile class. Should I be subclassing UIView or sublass NSObject and have an ivar of UIView. I was also planning to have a UIImageView inside the UIView to load the image that represents that bit of the map. Lastly, I wanted to load the view from a NIB.
Individually I know how to do each of these things but unsure of the best practice. Any thoughts?

Have you considered Core Animation layers? You can create a single view with a grid of layers each with their own bounds and position within the view. It sounds like it might give you what you need. On the phone, setting a layer's contents with an image is as simple as this:
CALayer *gridLayer = [CALayer layer];
[gridLayer setContents:(id)[gridImage CGImage]];
[gridLayer setBounds:[gridImage bounds]];
// Position the layer's center a x:25.0 y:25.0 within the view
[gridLayer setPosition:CGPointMake(25.0f, 25.0f)];
[[view layer] addSublayer:gridLayer];
The variable gridImage is a UIImage* you've allocated somewhere. You would just need to calculate your layer rects and positions and place them accordingly.

Make sure you have a clear definition of which particular classes/objects comprise your model, your controller and your view. Your data and data logic should be in the model, your display in the view and you should tie them together with the controller.
The simplest way to accomplish a grid is to use UIImageViews that are positioned by a single controller. The model will track the logical relationships between the map squares and relay that to the controller which will load the images into the UIIMageView. The controller will handle loading everything from nib.
However, using CALayers is the preferred cutting edge method. The basic model,controller, view relationship is the same except that the CALayers replace the ImageViews.

If I uderstand right, you can make subclass of UIImageView.

Related

Drawing class to allow layer control

I would like to implement a drawing class with the help of Quartz.
I want to be able to save parts of what is drawn on separate layers. I want these layers to be retrievable, so I can delete/hide/show layers on command.
Can I save multiple CGLayerRef as a NSMutableArray property of my class and then be able to retrieve them? If yes, can you point me to an example.
If there are any flaws in the "architecture" above, please point me to alternative solutions that could help me accomplish layered control over graphs.
Thank you.
CALayers that you create, can of course be stored in NSMutableArray, and you can work with them later on, usually by animating their properties, or asking them to redraw themselves.
Usually you create a custom UIView, create and manage layers within that view. Those layers are either member variables of that view, or you store them in an array. As things are happening in your app, your view animates the layers accordingly. Usually you want to react on touch events (which you also implement in that particular view - touchesBegan/Moved...) and animate the layers.
CALayer draws itself and caches the content for as long as you call [layer setNeedsDisplay], or it's bounds (size) are changed (well, if needsDisplayOnBoundsChange is true). Practically in all my apps I did, such redrawing happens very rarely - only if data are changed, and layer needs to redraw. Animating layers, transforming their size, rotation, changing position - layer is not redrawn during any of these. Hiding, showing, changing transparency - no redraw is required.
That "drawing class" you are talking about - you actually have only two options - either you extend CALayer and overwrite drawInContext:, or you create basic CALayer, set its delegate, and there you draw in drawLayer:inContext:. I personally prefer creating delegates.

what is the context passed into drawLayer:inContext:?

In Iphone development, I want to draw context in layer.
Then the question is:
What exactly is the context passed into drawLayer:inContext:? Is it the layer's contents's context or the UIview's context?
If this is the UIView's context, which UIView it is?
Thanks in advance.
The context being passed in belongs to the CALayer also returned by that delegate method. Usually, this is a display context, but it can also be an image or PDF context if the layer is manually drawn using -renderInContext:.
CALayers can exist on their own, or be used as the backing for a UIView. All UIViews have a layer behind them, which handles the actual display of that view's content. Drawing in a view actually draws on its layer, and, likewise, drawing in a CALayer that backs a UIView will appear to draw to the view.
As I said, you can create CALayers that exist as separate entities, and add them to existing layers as sublayers for display. At some point, there will need to be a UIView that hosts all of these sublayers within its backing layer in order for these layers to be seen on the iPhone's screen.
Note that according to the UIView class reference:
Since the view is the layer’s
delegate, you should never set the
view as a delegate of another CALayer
object. Additionally, you should never
change the delegate of this layer.
This means that for a UIView's layer, you would be handling the delegate method within the UIView in almost all cases, so the layer passed in to that method would be the view's layer. Sublayers can have anything as their delegate, because they are not attached to a particular view.
There is some information here: Providing Layer Content
If you must draw the layer’s content rather than loading it from an image, you implement the drawLayer:inContext: delegate method. The delegate is passed the layer for which content is required and a CGContextRef to draw the content in.
So normally is the context of your delegate object. In the case of UIVIew, the view itself is the delegate.

Is it possible to display an CALayer without an UIView?

As far as I can tell, I need an UIView (or subclass of UIView) to display an CALayer on screen, right?
Then, what's the point of using CALayer for saving memory? The only point I see is when I would add several sublayers to a CALayer. Then those sublayers would not get copied 3 times for all the different tree types like presentation tree, render tree and so on. Is that right?
You can add CALayers as sublayers of another CALayer. Each individual layer does not have to be backed by a view, but the root layer in the hierarchy must be backed by a UIView.
The point is not to avoid having copies of the CALayers, which are quite lightweight, but to avoid having copies of UIViews or, more specifically, the graphics contexts that back UIViews. Those take up considerably more memory.

How to add a naked CALayer as "subview" to a UIView?

I think that adding a CALayer as "subview", somehow, does save a lot of memory. A UIView always comes with 3 copies of it's content bitmap (presentation layer, render tree and another one, plus the view itself, so every pixel is saved 4 times). But how could that be done?
UIView is basically a wrapper for CALayer. So you can add a layer directly to the view's layer. This can be done by calling
[[theView layer] addSublayer:newLayer];

How to set order of display in Objective-C

I'm trying to mix between UIImageView drawing and line drawing using CGContext path draw commands, but the draw order is not correct.
I need to draw the path on top of the images but get the reverse.
The way I implemented it:
Setting UIImage within a UIImageView and adding the UIImageView as a subview of the main view.
Next I use the 'drawRect' (refresh draw function) to draw the path using the UIKit CGContextStrokePath function.
Now, other than using the Context image draw function directly (I want to avoid that because I reuse my image resources by several UIImageViews), is there a way to set the order correctly (a flag set maybe)?
The problem again is that lines/path are drawn before the UIImageViews (which are drawn automatically via the connection to the parent UIView), hence they are not visible over the images / UIImageViews.
Thanks
You can try implementing this through Quartz Core using layers (see the CALayer class documentation). Basically, you can have layers hierarchies. You associate each UIView to a different layer, then the layers are rendered together providing a single, composite layer. Besides, you can also apply transforms and animations to layers.
You need to import the QuartzCore header and do something like
#import <QuartzCore/QuartzCore.h>
UIView *mainView = [[UIView alloc] initWithFrame...
UIImageView *imageView = ...
CaLayer *mainViewLayer = mainView.layer;
[mainViewLayer addSubLayer: imageView.layer];
Then, when mainView appears on the screen, all the sublayers are merged together and rendered on screen. What happens is that each view renders its layer, while mainViewLayer is rendered merging together the two layers.
Let me know if this works for you.
You can have as many layers as you like. You can create an arbitrary hierarchi by using the CALayer methods
– addSublayer:
– removeFromSuperlayer
– insertSublayer:atIndex:
– insertSublayer:below:
– insertSublayer:above:
– replaceSublayer:with:
The contents of the topmost (in this case, last added) subview will appear in front of all other views. If you add an image subview and draw into its parent view, the image will appear in front of any drawing you do in that parent view.
You should draw into a new view you add to the main view only after adding the UIImageView.
You could also use bringSubviewToFront: and sendSubviewToBack: to manually reorder views if you need to.
(From your post, it's unclear which view you're drawing into, but I assume you're drawing into that main parent view which would explain this behavior.)