PDA

View Full Version : How to copy a UIView object by value / clone




daproject85
Sep 8, 2012, 07:25 PM
Hi Forum,

I have a bit of an issue here. So i have a UIViewController and it has a UIView property in which i draw stuff in and such. Now i have a UITableViewController with a UITableView and in my i want to have a custom cell in which i want to have "thumbnail" of the UIView. Each row is a Y= graph function.. etc y=2x. I have a delegate method which UITableViewController subclass calls and my UIGraphViewController implements in which i simply did...


-(UIView *)calculatorProgramsTableViewController:(CalculatorProgramTableViewController *)sender wantsFavProgramThumbnail:(id)program
{
self.program = program; //makes graphview (UIview object) draw the function first
return self.graphview; // returns the UIview object
}


but the problem is that once i do get the object in my UITableViewController sublclass.....well its passed by reference clearly so if i modify it the original view gets modified too. and it goes away when you go back to it. Please help



CodeBreaker
Sep 9, 2012, 11:03 AM
Hmm, you can always do


UIView *myColneView = [myOriginalView copy];


Any changes you make to the clone view will not be performed on the original view, i.e. they will be two different objects.

Also, if the view is static (no animations/updating contents), you can take a screenshot of your view and display it in your table.

daproject85
Sep 9, 2012, 11:08 AM
hey thanks for the reply my friend. So would i have to declare NSCOPYING poroto?

and would i have to implement the copywithzone or copy method? or its ready to use out of the box?

CodeBreaker
Sep 9, 2012, 11:14 AM
hey thanks for the reply my friend. So would i have to declare NSCOPYING poroto?

and would i have to implement the copywithzone or copy method? or its ready to use out of the box?


I am sorry I posted in haste. UIView does not confirm to the NSCopying protocol, and enabling it can be a bit of a hassle.

If I were you, I would save all the parameters that define your view as properties/ivars of the view itself. And then I would just create a new view with the exact same properties as the original.

daproject85
Sep 9, 2012, 11:24 AM
Wait so I am a bit confused :) so doing

Uiview *cloneview = [originalView copy]

Will not work? U don't need nscopying for that?

KnightWRX
Sep 9, 2012, 12:21 PM
How do you draw the UIView thumbnail exactly ? Instead of just using the reference to your UIView, you could force it to render itself once, by using its layer's methods, in the current context for your UITableView.

Something like :


[myView.layer renderInContext: UIGraphicsGetCurrentContext];


Without your thumbnail generation code, kinda hard to see how you could implement this though.

EDIT: Why not use UIImageView for the resulting thumbnail ? Even easier, just return the UIImageView :


-(UIImageView *)calculatorProgramsTableViewController:(CalculatorProgramTableViewController *)sender wantsFavProgramThumbnail:(id)program
{
self.program = program; //makes graphview (UIview object) draw the function first
UIGraphicsBeginImageContext([self.graphview.layer frame].size);

[self.graphview.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return [[[UIImageView alloc] initWithImage: outputImage] autorelease];
}

Duncan C
Sep 9, 2012, 02:40 PM
Hi Forum,

I have a bit of an issue here. So i have a UIViewController and it has a UIView property in which i draw stuff in and such. Now i have a UITableViewController with a UITableView and in my i want to have a custom cell in which i want to have "thumbnail" of the UIView. Each row is a Y= graph function.. etc y=2x. I have a delegate method which UITableViewController subclass calls and my UIGraphViewController implements in which i simply did...


-(UIView *)calculatorProgramsTableViewController:(CalculatorProgramTableViewController *)sender wantsFavProgramThumbnail:(id)program
{
self.program = program; //makes graphview (UIview object) draw the function first
return self.graphview; // returns the UIview object
}


but the problem is that once i do get the object in my UITableViewController sublclass.....well its passed by reference clearly so if i modify it the original view gets modified too. and it goes away when you go back to it. Please help

In general, you don't want to copy a view to save it's state.

iOS programming heavily relies on the MVC (Model View Controller) design pattern. In that design pattern, model objects store data, view objects display it, and controller objects mediate between the two and provide control logic.

A UIView is a view object. It should not be used to store data, only display it. What I would suggest doing is creating a data container object that describes all the settings (state values) that one of your custom view objects needs to display itself. Then give your view object a data container object as a property. Then your view controller can set up and install a data object in one of your views. That would trigger the view to draw itself.

You can implement NSCopying in your data container object, which is much cleaner and simpler. You'd just copy all the properties of your data container to the new copy.

The other posters have made good suggestions about how to handle the thumbnail issue. To create a thumbnail, create a graphics context and ask your view's layer to render itself into that context. Then copy the contents of the context into a UIImage and dispose of the context. (I think somebody already posted the code for those steps.) You can then use that image as a static snapshot of your custom view.

daproject85
Sep 10, 2012, 09:53 PM
Duncan,

thanks so much for the good info. I went with the second route that you suggested. I made a UIImage of my view and just made it small to fit in the cell. Here is my Code


UIGraphicsBeginImageContext(self.graphview.bounds.size);
[self.graphview.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


I am not going to lie... i do not really understand line 1, and 2.... what i am really trying to say is... yea sure i read the API but i dont really understand what they do or how would one know they would have to code line 1, then call the "renderIncontext on the layer ...and also whats the significance of the "layer" property

KnightWRX
Sep 11, 2012, 08:24 AM
I am not going to lie... i do not really understand line 1, and 2.... what i am really trying to say is... yea sure i read the API but i dont really understand what they do or how would one know they would have to code line 1, then call the "renderIncontext on the layer ...and also whats the significance of the "layer" property

The context is simple. You're creating a context you can have a handle on and control (get and set properties to, render in, etc..). A context is simply a big area of memory where any Quartz command will be applied, a sort of frame buffer, but being that you have control over it, it's simply your own output buffer for drawing commands.

For the layer property, you need to understand how the Quartz Extreme model works. Basically, everything is a layer that is submitted to compositor before being sent to the system frame buffer for display. Your application has layers, the OS has layers, other applications have layers. Depending on the position (z-index) of these layers, the transparency of the pixels on the layers, the compositor (Quartz) will blend or choose pixels accordingly to copy to the final frame buffer for display.

When you ask a View to render its layer in a context, you're asking it to basically give you what it is sending to the compositor, but in a memory space you own rather than the system's compositor. Then you can do what you wish with the layer info, modify it, apply it to another view's layer or... as you did here, generate a thumbnail of what that view (and only that view) contains.

The UI* methods from UIKit are simply an abstraction over the existing Quartz functions (CG prefixed functions, like CGCurrentContext() or CGBitmapContextCreate, etc..). If you really want to understand how this works, I would suggest reading the Quartz Programming guide available from Apple.

daproject85
Sep 12, 2012, 04:18 PM
The context is simple. You're creating a context you can have a handle on and control (get and set properties to, render in, etc..). A context is simply a big area of memory where any Quartz command will be applied, a sort of frame buffer, but being that you have control over it, it's simply your own output buffer for drawing commands.

For the layer property, you need to understand how the Quartz Extreme model works. Basically, everything is a layer that is submitted to compositor before being sent to the system frame buffer for display. Your application has layers, the OS has layers, other applications have layers. Depending on the position (z-index) of these layers, the transparency of the pixels on the layers, the compositor (Quartz) will blend or choose pixels accordingly to copy to the final frame buffer for display.

When you ask a View to render its layer in a context, you're asking it to basically give you what it is sending to the compositor, but in a memory space you own rather than the system's compositor. Then you can do what you wish with the layer info, modify it, apply it to another view's layer or... as you did here, generate a thumbnail of what that view (and only that view) contains.

The UI* methods from UIKit are simply an abstraction over the existing Quartz functions (CG prefixed functions, like CGCurrentContext() or CGBitmapContextCreate, etc..). If you really want to understand how this works, I would suggest reading the Quartz Programming guide available from Apple.

thanks alot KnightWRX

so i am going to try to take a stab at this...
Line 1: basically we have to say here is the size of the area I want to work on or what I am modifying would be this size.

Line 2: we take the GraphView layer (the sheet we drew graphview on with all its subviews etc) and we say this is the layer we want to do all the rendering in.

Did i get it right?
and 1 question: renderInContext:UIGraphicsGetCurrentContext() , the how does the UIgraphicsGetCurrentContext know what is the "current context"? is it from line 1 where I said the size? or what