Any method to get layer's parent view? - ios

Yes, you read it right "layer's parent view". I'm not sure if its the right term to use but what I mean is, I have a layer added as sublayer of a view. I wanted to know if there is any way to get the view from the layer.

This question is quite old, but accessing CALayer's parent view is easy as:
layer.delegate
If you look into a debugger, you'll see that the delegate property is populated with the UIView containing the Layer, and if not, you can recursively check until you get the main CALayer directly hosted in the view.
Source: https://developer.apple.com/documentation/quartzcore/calayer/1410984-delegate

You can perform this using KVC.
UIView *View1=[UIView new];
View1.tag=1;
[View1.layer setValue:View1 forKey:#"LayerObject"];
UIView *View2=[UIView new];
View2.tag=2;
[View2.layer setValue:View2 forKey:#"LayerObject"];
NSLog(#"Your Layers superView = %#",[View2.layer.superlayer valueForKey:#"LayerObject"]);
This will return your layer's parent View.

OK, so if you have a commonly-used CALayer-subclass and you need to "call into the view" then it looks to me like that view will need to conform to a protocol in order for this to work correctly.
In that case you will need to store a reference to the view within the layer using KVC:
[layer setValue:view forKey:MyViewKey];
Where MyViewKey can be declared as part of the CALayer-subclass code:
extern NSString * const MyViewKey;
and will be defined something like:
NSString * const MyViewKey = #"Hello Mum";

Related

What is the point of declaring a variable as one class, and allocating memory to it with another class?

For example:
UIImageView * imageView = [[MyCustomImageView alloc] init];
What is the benefit of doing this? Why not do?:
MyCustomImageView * imageView = [[MyCustomImageView alloc] init];
The benefit is that you can hide implementation details to yourself and to the outside.
If you are going to return this value from a method for example, the outside may not care about what kind of image view it is - as long as it is some kind of it! If it's a private class you are creating, you may not even want to expose that this class exists to the outside.
In other languages with proper interfaces, this is a more well known pattern. This article is a good read.
There are important uses for both. In both cases, I'm assuming that MyCustomImageView inherits from UIImageView...
UIImageView * imageView = [[MyCustomImageView alloc] init];
Assigning a different object to its parent's type is used to extend the standard functionality of UIImageView by overriding public methods and properties and only those. You could, for example, deliver an entirely different image presentation by implementing MyCustomImageView layoutSubviews or drawRect with no other logic in your view controller.
MyCustomImageView * imageView = [[MyCustomImageView alloc] init];
With a declared pointer to MyCustomImageView you can override the public methods and declare new properties and methods that your app requires like invertImage, blinkImage, etc.

Drawing an object of NSString in a Rectangle.

I'm trying to put a string in a rectangular form via drawInRect:withAttributes: method. The official documentation writes: "Draws the receiver with the font and other display characteristics of the given attributes, within the specified rectangle in the currently focused UIView."
This action should be done on a button event. But it doesn't work at all. Nothing is shown.
The code that I use:
- (IBAction)buttonPressed:(id)sender
{
// some area on the top (a part of UIView)
CGRect rect = CGRectMake(50, 50, 100, 100);
// my text
NSString *str = #"Eh, what's wrong with this code?";
// crappy attributes
NSDictionary *attributes = #{NSFontAttributeName: [UIFont boldSystemFontOfSize:8]};
// and finally passing a message
[str drawInRect:rect withAttributes:attributes];
}
Is there a particular kind of areas that might be used for drawing strings in rects? Or is it possible to draw a string in a rect where I'd like it to be? Please, help me understand it better!
But it doesn't work at all. Nothing is shown.
That's because no drawing context is set up when you run that code. You're looking at drawing as something that you can do whenever you feel like it, but in fact drawing is something that you should only do when the system asks you to. At other times, you should instead remember what you want to draw, and then draw it when the time comes.
When is "the time" to draw? It's when your view's -drawRect: method is called. Since your method is an action, it looks like you're probably trying to do the drawing in a view controller. What you want to do instead is to subclass UIView and override -drawRect: to draw what you want.
It doesn't work that way, you first need to make a CGContextRef where you will draw the text.
Your code is fine, just place everything within drawRect.

iOS creating PDF from UIViews

I am currently creating PDF documents from a UIView in iOS by using CALayer and the renderInContext method.
The problem I am facing is the sharpness of labels. I have created a UILabel subclass that overrides drawLayer like so:
/** Overriding this CALayer delegate method is the magic that allows us to draw a vector version of the label into the layer instead of the default unscalable ugly bitmap */
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
BOOL isPDF = !CGRectIsEmpty(UIGraphicsGetPDFContextBounds());
if (!layer.shouldRasterize && isPDF)
[self drawRect:self.bounds]; // draw unrasterized
else
[super drawLayer:layer inContext:ctx];
}
This method lets me draw nice crisp text, however, the problem is with other views that I don't have control over. Is there any method that would allow me to do something similar for labels embedded in UITableView or UIButton. I guess I'm looking for a way to iterate through the view stack and do something to let me draw sharper text.
Here is an example:
This text renders nicely (my custom UILabel subclass)
The text in a standard segmented control isn't as sharp:
Edit: I am getting the context to draw into my PDF as follows:
UIGraphicsBeginPDFContextToData(self.pdfData, CGRectZero, nil);
pdfContext = UIGraphicsGetCurrentContext();
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
[view.layer renderInContext:pdfContext];
I ended up traversing the view hierarchy and setting every UILabel to my custom subclass that overrides drawLayer.
Here is how I traverse the views:
+(void) dumpView:(UIView*) aView indent:(NSString*) indent {
if (aView) {
NSLog(#"%#%#", indent, aView); // dump this view
if ([aView isKindOfClass:[UILabel class]])
[AFGPDFDocument setClassForLabel:aView];
if (aView.subviews.count > 0) {
NSString* subIndent = [[NSString alloc] initWithFormat:#"%#%#",
indent, ([indent length]/2)%2==0 ? #"| " : #": "];
for (UIView* aSubview in aView.subviews)
[AFGPDFDocument dumpView:aSubview indent:subIndent];
}
}
}
And how I change the class:
+(void) setClassForLabel: (UIView*) label {
static Class myFancyObjectClass;
myFancyObjectClass = objc_getClass("UIPDFLabel");
object_setClass(label, myFancyObjectClass);
}
The comparison:
Old:
New:
Not sure if there is a better way to do this, but it seems to work for my purposes.
EDIT: Found a more generic way to do this that doesn't involve changing the class or traversing through the whole view hierarchy. I am using method swizzling. This method also lets you do cool things like surrounding every view with a border if you want. First I created a category UIView+PDF with my custom implementation of the drawLayer method, then in the load method I use the following:
// The "+ load" method is called once, very early in the application life-cycle.
// It's called even before the "main" function is called. Beware: there's no
// autorelease pool at this point, so avoid Objective-C calls.
Method original, swizzle;
// Get the "- (void) drawLayer:inContext:" method.
original = class_getInstanceMethod(self, #selector(drawLayer:inContext:));
// Get the "- (void)swizzled_drawLayer:inContext:" method.
swizzle = class_getInstanceMethod(self, #selector(swizzled_drawLayer:inContext:));
// Swap their implementations.
method_exchangeImplementations(original, swizzle);
Worked from the example here: http://darkdust.net/writings/objective-c/method-swizzling

objective-c: alloc vs synthesise/retain

I have a beginner's question. I would like to have access to an UIView in all parts of my code (as opposed to a single method/function).
I therefore declared it in the #interface section
UIView *calendarView;
and then #property (nonatomic, retain) UIView *calendarView. In the #implementation I have #synthesise calendarView.
Now I would like to define the frame of this view and coded the following:
CGRect calendarFrame = CGRectMake(170, 8, 200, 50);
calendarView = [[UIView alloc] initWithFrame:calendarFrame];
But I am now wondering if I am doing something seriously wrong here, as calendarView has already been retained and synthesised. Surely UIView alloc is superfluous or even bound to make the app crash, as I am running into memory problems, right?
So I though I should code this instead of the two previous lines, but it only had the effect that the calendarView is not shown at all:
[calendarView setFrame:CGRectMake(170, 8, 200, 50)];
So my question is if I really need to alloc the view before I can use it? Or is there yet another solution?
you can retain an object only after you have it in memory(ie you have alloc'ed it)..So below given code is correct and needed..
CGRect calendarFrame = CGRectMake(170, 8, 200, 50);
calendarView = [[UIView alloc] initWithFrame:calendarFrame];//you have alloc'ed once
now you will add this view to a parentView. For example I am inside a UIViewController implementation..
[self.view addSubView:calendarView]; //now only your retain takes place. //So now calenderView has to release twice..Once by you (since you alloced it) and once by viewcontroller (since it has retained it)...
[calendarView release];//we are releasing once but the object will not be removed from memory since it is retained by viewController..Our part in memory management is over..Now when this viewController get dealloced it releases
You can use this calendarView throughout the implementation of this UIViewController..
-(void)dealloc{
[super dealloc];//should be last in dealloc..Now the entire controller will be dealloced along with the calenderView which is retained by viewController and the memory will be freed for future uses..
}
These are some useful tutorials..easier to understand than apple's documentation..But read Apple's documentation too..
http://ferasferas.wordpress.com/2010/12/05/introduction-to-memory-management-on-iphone/
http://iosdevelopertips.com/objective-c/memory-management.html
http://mauvilasoftware.com/iphone_software_development/2008/01/iphone-memory-management-a-bri.html
https://humblecoder.blogspot.com/2009/08/iphone-tutorial-memory-management.html
Synthesizing a property doesn't actually instantiate an object for you. You'll still need your alloc and init methods.
In the code above, if you're wanting to use the property, you should be using self.calendarView rather than just calendarView. (Doing the latter is bypassing the property and using the instance variable directly, which is usually not what you want, with the possible exception of in your dealloc method.)
The one final change you should make: given that your property is marked retain, it'll handle keeping your object around itself. Therefore you should autorelease the object you're putting into it. Try this:
self.calendarView = [[[UIView alloc] initWithFrame:calendarFrame] autorelease];
You actually are not doing anythin wrong in your first example other than a possible memory leak. You just have the wrong thinking that your calendarView is already retained because that was how you defined your property, which is not true. Defining your property as retain only means that when you call self.calendarView = someotherview, someotherview will be retained, the old value in calendarView will be released and calendarView will then be set to someotherview. Using calendarView without self will not provide you with any memory management rules like the property and that is why your first example is ok. You may want your code to look more like this.
CGRect calendarFrame = CGRectMake(170, 8, 200, 50);
self.calendarView = [[[UIView alloc] initWithFrame:calendarFrame] autorelease];
Yes,
You either need to alloc OR get it from others sources (release it in dealloc because you are retaining it ) before using UIView.
Use below
CGRect calendarFrame = CGRectMake(170, 8, 200, 50);
self.calendarView = [[[UIView alloc] initWithFrame:calendarFrame] autorelease];
Read apple documentation for memory management..
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
I therefore declared it in the #interface section
right, that just reserves space and a label to access an ivar with the type+name
and then #property (nonatomic, retain) UIView *calendarView.
right, that declares the accessors (setter+getter), and operation if synthesized
In the #implementation I have #synthesise calendarView.
that defines (implements) the accessors declared by the property declaration.
Now I would like to define the frame of this view and coded the following:
...
But I am now wondering if I am doing something seriously wrong here, as calendarView has already been retained and synthesised. Surely UIView alloc is superfluous or even bound to make the app crash, as I am running into memory problems, right?
for one, your memory management is off:
CGRect calendarFrame = CGRectMake(170, 8, 200, 50);
UIView * view = [[UIView alloc] initWithFrame:calendarFrame];
self.calendarView = view; // use the setter, unless in init... or dealloc
[view release], view = 0;
two:
you will not usually have much use in creating a UIView. typically, you will create a subclass of it.
three (to get to your question):
there's nothing wrong with that. the variable will be nil until it's been set. the field is not default initialized for the declared type -- well, it is, but the type is in fact a pointer, so the result is that it is initialized to nil. you can either create a view or pass it in from someplace else. the view will be nil/NULL/0 until that point.
So I though I should code this instead of the two previous lines, but it only had the effect that the calendarView is not shown at all:
[calendarView setFrame:CGRectMake(170, 8, 200, 50)];
So my question is if I really need to alloc the view before I can use it? Or is there yet another solution?
stepping back to point #2 in more detail: you'll want to create a subclass in most cases. UIView/NSView does no drawing by default, but it can be used as a view container. therefore, you may want to start with some existing subclasses to get familiar with the system-supplied views.
once you have a handle on that, try implementing your own subclasses and overriding drawRect:.
many beginners like using Interface Builder (now integrated into Xc4) -- a WYSIWYG view editor.

MkMapView cancel loading google tiles

I have a case where I need to import an overlay map in top of MkMapView.
The overlay totally covers the google tiles below so there is no need loading, plus it adds overhead to the app.
Is there a way to tell mkMapView to stop loading tiles?
Actually there are 2 ways to implement the real "Hide Google tiles" method (johndope solution only puts an overlay on top of it but doesn't prevent the tiles from loading).
Beware that option one described below might get your application rejected while option 2 will not but is a bit more complex.
Common Part: Retrieve the MKMapTileView object
Inside each MKMapView lies an undocumented class of type: MKMapTileView. Retrieving it is NOT a reason for rejection. In this code the MKMapView instance will be called mapView
UIView* scrollview = [[[[mapView subviews] objectAtIndex:0] subviews] objectAtIndex:0];
UIView* mkTiles = [[scrollview subviews] objectAtIndex:0]; // <- MKMapTileView instance
Option 1: Undocumented Method (!! can be a reason for rejection !! )
if ( [mkTiles respondsToSelector:#selector(setDrawingEnabled:)])
[mkTiles performSelector:#selector(setDrawingEnabled:) withObject:(id)NO];
This will prevent the undocumented method setDrawingEnabled to be called on the MKMapTileView instance.
Option 2: Method Swizzling
Outside of your controller implementation you will write someting like:
// Import runtime.h to unleash the power of objective C
#import <objc/runtime.h>
// this will hold the old drawLayer:inContext: implementation
static void (*_origDrawLayerInContext)(id, SEL, CALayer*, CGContextRef);
// this will override the drawLayer:inContext: method
static void OverrideDrawLayerInContext(UIView *self, SEL _cmd, CALayer *layer, CGContextRef context)
{
// uncommenting this next line will still perform the old behavior
//_origDrawLayerInContext(self, _cmd, layer, context);
// change colors if needed so that you don't have a black background
layer.backgroundColor = RGB(35, 160, 211).CGColor;
CGContextSetRGBFillColor(context, 35/255.0f, 160/255.0f, 211/255.0f, 1.0f);
CGContextFillRect(context, layer.bounds);
}
And somewhere in your code (once your map view is loaded!) :
// Retrieve original method object
Method origMethod = class_getInstanceMethod([mkTiles class],
#selector(drawLayer:inContext:));
// from this method, retrieve its implementation (actual work done)
_origDrawLayerInContext = (void *)method_getImplementation(origMethod);
// override this method with the one you created
if(!class_addMethod([mkTiles class],
#selector(drawLayer:inContext:),
(IMP)OverrideDrawLayerInContext,
method_getTypeEncoding(origMethod)))
{
method_setImplementation(origMethod, (IMP)OverrideDrawLayerInContext);
}
Hope this helps anyone, this code was originally described in this blog post.
As far as I know you can't prevent MKMapView from loading Google Maps tiles, and there's a chance your app will be rejected if it covers up the Google logo while displaying an MKMapView – you may want to consider writing a custom UIScrollView to display your map instead.
I encountered a related problem but in my case my overlay didn't cover all the google tiles.
If anyone has this problem, and is trying to stop loading the google tiles, I managed to superimpose one gray tile(256x256) overlay over the google map tiles.
To do this, the top level tile must be
/FakeTiles/1/1/0.png and add this directory to resources to project.
(N.B. don't drag this into project > Add files > Folders > Create folder references for any folders)
//hack to overlay grey tiles
NSString *fakeTileDirectory = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"FakeTiles"];
TileOverlay *greyOverlay = [[TileOverlay alloc] initWithTileDirectory:fakeTileDirectory];
[mapView addOverlay:greyOverlay];
[greyOverlay release];
then add your custom tile overlay.
Then you need this code to scale the grey tile
Calculating tiles to display in a MapRect when "over-zoomed" beyond the overlay tile set

Resources