I am trying to display some annotations on a map. I want to use the MKMapView class because of the way it handles annotations, it's great for me. But I have my custom map system which works with its own view. I have tried to implement method swizzling as suggested in this answer: https://stackoverflow.com/a/10702022/1152596 , but I had no luck. The tiles are not being displayed, which is ok, but the background is not transparent, it has a gray-like color. See screenshot below:
The black path is my annotation. Behind is the view with my own map I don't get to see. I'm sure it's behind, if I don't add MKMapView I can see it.
I know what I'm trying here is very hacky, but no alternative came to my mind.
The code for the swizzling from the linked answer is:
I define:
// 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);
}
In my viewDidLoad method:
UIView* scrollview = [[[[mapView subviews] objectAtIndex:0] subviews] objectAtIndex:0];
UIView* mkTiles = [[scrollview subviews] objectAtIndex:0]; // <- MKMapTileView instance
// 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);
}
Related
I have a UITableView that I am attempting to expand and collapse using its beginUpdates and endUpdates methods and have a drop shadow displayed as that's happening. In my custom UITableViewCell, I have a layer which I create a shadow for in layoutSubviews:
self.shadowLayer.shadowColor = self.shadowColor.CGColor;
self.shadowLayer.shadowOffset = CGSizeMake(self.shadowOffsetWidth, self.shadowOffsetHeight);
self.shadowLayer.shadowOpacity = self.shadowOpacity;
self.shadowLayer.masksToBounds = NO;
self.shadowLayer.frame = self.layer.bounds;
// this is extremely important for performance when drawing shadows
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.shadowLayer.frame cornerRadius:self.cornerRadius];
self.shadowLayer.shadowPath = shadowPath.CGPath;
I add this layer to the UITableViewCell in viewDidLoad:
self.shadowLayer = [CALayer layer];
self.shadowLayer.backgroundColor = [UIColor whiteColor].CGColor;
[self.layer insertSublayer:self.shadowLayer below:self.contentView.layer];
As I understand it, when I call beginUpdates, an implicit CALayerTransaction is made for the current run loop if none exists. Additionally, layoutSubviews also gets called. The problem here is that the resulting shadow is drawn immediately based on the new size of the UITableViewCell. I really need to shadow to continue to cast in the expected way as the actual layer is animating.
Since my created layer is not a backing CALayer it animates without explicitly specifying a CATransaction, which is expected. But, as I understand it, I really need some way to grab hold of beginUpdates/endUpdates CATransaction and perform the animation in that. How do I do that, if at all?
So I guess you have something like this:
(I turned on “Debug > Slow Animations” in the simulator.) And you don't like the way the shadow jumps to its new size. You want this instead:
You can find my test project in this github repository.
See #horseshoe7's answer for a Swift translation.
It is tricky but not impossible to pick up the animation parameters and add an animation in the table view's animation block. The trickiest part is that you need to update the shadowPath in the layoutSubviews method of the shadowed view itself, or of the shadowed view's immediate superview. In my demo video, that means that the shadowPath needs to be updated by the layoutSubviews method of the green box view or the green box's immediate superview.
I chose to create a ShadowingView class whose only job is to draw and animate the shadow of one of its subviews. Here's the interface:
#interface ShadowingView : UIView
#property (nonatomic, strong) IBOutlet UIView *shadowedView;
#end
To use ShadowingView, I added it to my cell view in my storyboard. Actually it's nested inside a stack view inside the cell. Then I added the green box as a subview of the ShadowingView and connected the shadowedView outlet to the green box.
The ShadowingView implementation has three parts. One is its layoutSubviews method, which sets up the layer shadow properties on its own layer to draw a shadow around its shadowedView subview:
#implementation ShadowingView
- (void)layoutSubviews {
[super layoutSubviews];
CALayer *layer = self.layer;
layer.backgroundColor = nil;
CALayer *shadowedLayer = self.shadowedView.layer;
if (shadowedLayer == nil) {
layer.shadowColor = nil;
return;
}
NSAssert(shadowedLayer.superlayer == layer, #"shadowedView must be my direct subview");
layer.shadowColor = UIColor.blackColor.CGColor;
layer.shadowOffset = CGSizeMake(0, 1);
layer.shadowOpacity = 0.5;
layer.shadowRadius = 3;
layer.masksToBounds = NO;
CGFloat radius = shadowedLayer.cornerRadius;
layer.shadowPath = CGPathCreateWithRoundedRect(shadowedLayer.frame, radius, radius, nil);
}
When this method is run inside an animation block (as is the case when the table view animates a change in the size of a cell), and the method sets shadowPath, Core Animation looks for an “action” to run after updating shadowPath. One of the ways it looks is by sending actionForLayer:forKey: to the layer's delegate, and the delegate is the ShadowingView. So we override actionForLayer:forKey: to provide an action if possible and appropriate. If we can't, we just call super.
It is important to understand that Core Animation asks for the action from inside the shadowPath setter, before actually changing the value of shadowPath.
To provide the action, we make sure the key is #"shadowPath", that there is an existing value for shadowPath, and that there is already an animation on the layer for bounds.size. Why do we look for an existing bounds.size animation? Because that existing animation has the duration and timing function we should use to animate shadowPath. If everything is in order, we grab the existing shadowPath, make a copy of the animation, store them in an action, and return the action:
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
if (![event isEqualToString:#"shadowPath"]) { return [super actionForLayer:layer forKey:event]; }
CGPathRef priorPath = layer.shadowPath;
if (priorPath == NULL) { return [super actionForLayer:layer forKey:event]; }
CAAnimation *sizeAnimation = [layer animationForKey:#"bounds.size"];
if (![sizeAnimation isKindOfClass:[CABasicAnimation class]]) { return [super actionForLayer:layer forKey:event]; }
CABasicAnimation *animation = [sizeAnimation copy];
animation.keyPath = #"shadowPath";
ShadowingViewAction *action = [[ShadowingViewAction alloc] init];
action.priorPath = priorPath;
action.pendingAnimation = animation;
return action;
}
#end
What does the action look like? Here's the interface:
#interface ShadowingViewAction : NSObject <CAAction>
#property (nonatomic, strong) CABasicAnimation *pendingAnimation;
#property (nonatomic) CGPathRef priorPath;
#end
The implementation requires a runActionForKey:object:arguments: method. In this method, we update the animation that we created in actionForLayer:forKey: using the saved-away old shadowPath and the new shadowPath, and then we add the animation to the layer.
We also need to manage the retain count of the saved path, because ARC doesn't manage CGPath objects.
#implementation ShadowingViewAction
- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict {
if (![anObject isKindOfClass:[CALayer class]] || _pendingAnimation == nil) { return; }
CALayer *layer = anObject;
_pendingAnimation.fromValue = (__bridge id)_priorPath;
_pendingAnimation.toValue = (__bridge id)layer.shadowPath;
[layer addAnimation:_pendingAnimation forKey:#"shadowPath"];
}
- (void)setPriorPath:(CGPathRef)priorPath {
CGPathRetain(priorPath);
CGPathRelease(_priorPath);
_priorPath = priorPath;
}
- (void)dealloc {
CGPathRelease(_priorPath);
}
#end
This is Rob Mayoff's answer written in Swift. Could save someone some time.
Please don't upvote this. Upvote Rob Mayoff's solution. It is awesome, and correct. (Note from mayoff: why not upvote both? 😉)
import UIKit
class AnimatingShadowView: UIView {
struct DropShadowParameters {
var shadowOpacity: Float = 0
var shadowColor: UIColor? = .black
var shadowRadius: CGFloat = 0
var shadowOffset: CGSize = .zero
static let defaultParameters = DropShadowParameters(shadowOpacity: 0.15,
shadowColor: .black,
shadowRadius: 5,
shadowOffset: CGSize(width: 0, height: 1))
}
#IBOutlet weak var contentView: UIView! // no sense in have a shadowView without content!
var shadowParameters: DropShadowParameters = DropShadowParameters.defaultParameters
private func apply(dropShadow: DropShadowParameters) {
let layer = self.layer
layer.shadowColor = dropShadow.shadowColor?.cgColor
layer.shadowOffset = dropShadow.shadowOffset
layer.shadowOpacity = dropShadow.shadowOpacity
layer.shadowRadius = dropShadow.shadowRadius
layer.masksToBounds = false
}
override func layoutSubviews() {
super.layoutSubviews()
let layer = self.layer
layer.backgroundColor = nil
let contentLayer = self.contentView.layer
assert(contentLayer.superlayer == layer, "contentView must be a direct subview of AnimatingShadowView!")
self.apply(dropShadow: self.shadowParameters)
let radius = contentLayer.cornerRadius
layer.shadowPath = UIBezierPath(roundedRect: contentLayer.frame, cornerRadius: radius).cgPath
}
override func action(for layer: CALayer, forKey event: String) -> CAAction? {
guard event == "shadowPath" else {
return super.action(for: layer, forKey: event)
}
guard let priorPath = layer.shadowPath else {
return super.action(for: layer, forKey: event)
}
guard let sizeAnimation = layer.animation(forKey: "bounds.size") as? CABasicAnimation else {
return super.action(for: layer, forKey: event)
}
let animation = sizeAnimation.copy() as! CABasicAnimation
animation.keyPath = "shadowPath"
let action = ShadowingViewAction()
action.priorPath = priorPath
action.pendingAnimation = animation
return action
}
}
private class ShadowingViewAction: NSObject, CAAction {
var pendingAnimation: CABasicAnimation? = nil
var priorPath: CGPath? = nil
// CAAction Protocol
func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
guard let layer = anObject as? CALayer, let animation = self.pendingAnimation else {
return
}
animation.fromValue = self.priorPath
animation.toValue = layer.shadowPath
layer.add(animation, forKey: "shadowPath")
}
}
Assuming that you're manually setting your shadowPath, here's a solution inspired by the others here that accomplishes the same thing using less code.
Note that I'm intentionally constructing my own CABasicAnimation rather than copying the bounds.size animation exactly, as in my own tests I found that toggling the copied animation while it was still in progress could cause the animation to snap to it's toValue, rather than transitioning smoothly from its current value.
class ViewWithAutosizedShadowPath: UIView {
override func layoutSubviews() {
super.layoutSubviews()
let oldShadowPath = layer.shadowPath
let newShadowPath = CGPath(rect: bounds, transform: nil)
if let boundsAnimation = layer.animation(forKey: "bounds.size") as? CABasicAnimation {
let shadowPathAnimation = CABasicAnimation(keyPath: "shadowPath")
shadowPathAnimation.duration = boundsAnimation.duration
shadowPathAnimation.timingFunction = boundsAnimation.timingFunction
shadowPathAnimation.fromValue = oldShadowPath
shadowPathAnimation.toValue = newShadowPath
layer.add(shadowPathAnimation, forKey: "shadowPath")
}
layer.shadowPath = newShadowPath
}
}
UITableView is likely not creating a CATransaction, or if it is, it's waiting until after you end the updates. My understanding is that table views just coalesce all changes between those functions and then creates the animations as necessary. You don't have a way to get a handle on the actual animation parameters it's committing, because we don't know when that actually happens. The same thing happens when you animate a content offset change in UIScrollView: the system provides no context about the animation itself, which is frustrating. There is also no way to query the system for current CATransactions.
Probably the best you can do is inspect the animation that UITableView is creating and just mimic the same timing parameters in your own animation. Swizzling add(_:forKey:) on CALayer can allow you to inspect all animations being added. You certainly don't want to actually ship with this, but I often use this technique in debugging to figure out what animations are being added and what their properties are.
I suspect that you're going to have to commit your own shadow layer animations in tableView(_:willDisplayCell:for:row:) for the appropriate cells.
I'm using Xcode, SpriteKit to create a game. 2 questions:
1) I need the whole scene to rotate whenever a function is called:
- (void)mouseClick {
// RotateWholeScene somehow
}
I know about rotating a sprite by changing its zrotation, but changing scene.zrotation doesn’t work.
Also, some things in the scene need to stay in the same position, like the score, etc.
I’d prefer it if there is a way which I can rotate the whole scene as if the camera view has changed, as there are calculations (like falling objects) which would be much easier to do if they aren’t needed to be changed in the code.
2) Right now the program creates a sprite each frame, and places the sprite into an array (which I declared at the start).
NSMutableArray *SomeArray = [NSMutableArray arrayWithObjects: nil];
And later on in the code:
- (void)CalledEachFrame {
[self addChild: SomeSprite];
[SomeArray addObject: SomeSprite];
}
Elsewhere in the code, I need:
- (void)AlsoCalledEachFrame {
for (int i; i < X; i++) {
SomeArray[i].position = A;
}
}
This (and several other attempts) doesn't work, and I get an error:
***EDIT: My bad. In the image, I showed, I set the position to an integer rather than a CGPoint. Nevertheless, the same error occur even if I put a CGPoint instead of those 99s.
You asking two questions at once here. I should discourage that. Nevertheless, here goes:
1) I faced a similar problem and my solution to this problem was putting the scene nodes and the hud nodes each in a separate child nodes of the SKScene subclass, like so:
#interface SKSceneSubclass : SKScene
// ...
#property (strong, nonatomic, readonly) SKNode *sceneContents;
#property (strong, nonatomic, readonly) SKNode *hud;
#end
In the scene initializer, I'd put:
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.sceneContents = [SKNode new];
[self addChild:self.sceneContents];
self.hud = [SKNode new];
[self addChild:self.hud];
self.hud.zPosition = 20.f; // ensures the hud on top of sceneContents
// your custom stuff
// ...
}
return self;
}
This requires you to decide upon adding child nodes which node (sceneContents or hud) will be their parent.
[self.sceneContents addChild:newSprite];
[self.hud addChild:someCoolHudButton];
Then, you can just run [SKAction rotateByAngle:angle duration:duration] on the sceneContents node. The hud and its children will not be affected. I am currently employing this method to move and zoom sceneContents while having a stationary HUD which behaves as it should.
2) The problem is that you are not telling the compiler the item you're accessing is an SKNode object, so the compiler does not allow you to access the position property. If you are certain only the array will only contain SKNodes or subclasses thereof, use the forin iterator method.
for (<#type *object#> in <#collection#>) {
<#statements#>
}
In your case, it would look like this.
CGPoint newPosition = CGPointMake(x, y);
for (SKNode *node in SomeArray) {
node.position = newPosition;
}
You can manually rotate the view with this code:
CGAffineTransform transform = CGAffineTransformMakeRotation(-0.1); // change value to desires angle
self.view.transform = transform;
I am trying to remove an object instance, but not quite sure how to do it in Objective C?
I would like to get rid of that ellipse I created put on the screen
#import "C4WorkSpace.h"
#import <UIKit/UIKit.h>
C4Shape * myshape; // [term] declaration
C4Shape * secondshape;
CGRect myrect; // core graphics rectangle declaration
int x_point; // integer (whole)
int y_point;
#implementation C4WorkSpace
-(void)setup
{
// created a core graphics rectangle
myrect = CGRectMake(0, 0, 100, 100);
// [term] definition (when you allocate, make, or instantiate)
myshape = [C4Shape ellipse:myrect];
// preview of week 3
[myshape addGesture:PAN name:#"pan" action:#"move:"];
//Display the Shape
[self.canvas addShape:myshape];
}
-(void)touchesBegan {
}
#end
I am really new to Objective-C, Please explain it in a bit easy language.
When you're working with C4 (or iOS / Objective-C) you're working with objects that are views. The things you see (like shapes, or images, or any other kind of visual element) actually sitting inside invisible little windows.
So, when you add something to the canvas, you're actually adding a view to the canvas. The canvas itself is also a view.
When adding views to one another the app makes a "hierarchy" so that if you add a shape to the canvas, the canvas becomes the shape's superview and the shape becomes a subview of the canvas.
Now, to answer your question (I modified your code):
#import "C4WorkSpace.h"
#implementation C4WorkSpace {
C4Shape * myshape; // [term] declaration
CGRect myrect; // core graphics rectangle declaration
}
-(void)setup {
myrect = CGRectMake(0, 0, 100, 100);
myshape = [C4Shape ellipse:myrect];
[myshape addGesture:PAN name:#"pan" action:#"move:"];
[self.canvas addShape:myshape];
}
-(void)touchesBegan {
//check to see if the shape is already in another view
if (myshape.superview == nil) {
//if not, add it to the canvas
[self.canvas addShape:myshape];
} else {
//otherwise remove it from the canvas
[myshape removeFromSuperview];
}
}
#end
I changed the touchesBegan method to add / remove the shape from the canvas. The method works like this:
It first checks to see if the shape has a superview
If it doesn't, that means its not on the canvas so it adds it
If it does have one, it removes it by calling [shape removeFromSuperview];
When you run the example you'll notice that you can toggle it on and off the canvas. You can do this because the shape itself is an object and you've created it in memory and kept it around.
If you ever want to completely destroy the shape object, you could remove it from the canvas and then call shape = nil;
I'm trying to create a function to change the size of something like a UILabel or UIButton without having to type the three lines out every time. This is what I have.
-(void)setObject:(UIControl*)object SizeWidth:(NSInteger)width Height:(NSInteger)height
{
CGRect labelFrame = object.frame;
labelFrame.size = CGSizeMake(width, height);
object.frame = labelFrame;
}
However, when I give (UIControl*)object a UILabel, it says "incompatible pointer types". How can I fix this to work for anything I can put on a UIView?
UILabel is not a subclass of UIControl, it inherits from UIView.
Try changing UIControl to UIView:
-(void)setObject:(UIView*)object SizeWidth:(NSInteger)width Height:(NSInteger)height
{
CGRect labelFrame = object.frame;
labelFrame.size = CGSizeMake(width, height);
object.frame = labelFrame;
}
(UIControl inherits from UIView anyway, and frame is a UIView property)
Label is not a subclass of UIControl. You can use UIView in place of UIControl.
Here is the hierarchy for UILabel
UILabel: UIView : UIResponder : NSObject
-(void)setObject:(UIView*)object SizeWidth:(NSInteger)width Height:(NSInteger)height
{
CGRect labelFrame = object.frame;
labelFrame.size = CGSizeMake(width, height);
object.frame = labelFrame;
}
One suggestion for you is, the method name seems kind of odd to me. You can write a simple category to update the size for UIView. With the following category you can simply call
[myLabel setWidth:20 andHeight:20];
In UIView + MyCategory.h
#import <UIKit/UIKit.h>
#interface UIView (MyCategory)
- (void)setWidth:(NSInteger)aWidth andHeight:(NSInteger)aHeight;
#end
In UIView + MyCategory.m
#import "UIView + MyCategory.h"
#implementation UIView (MyCategory)
- (void)setWidth:(NSInteger)aWidth andHeight:(NSInteger)aHeight;
{
CGRect frameToUpdate = self.frame;
frameToUpdate.size = CGSizeMake(aWidth, aHeight);
self.frame = frameToUpdate;
}
#end
And to address the actual problem you're trying to solve, I highly recommend using this set of helpers https://github.com/kreeger/BDKGeometry
Following Obj-C style conventions (as selecting the right tools for the job) make it more efficient for others to read and comprehend our code. The Objective-C style here needs a bit of cleanup. See my notes after the source if you're interested. On to a more concise way to do this:
You can go the class method route (perhaps in a view manipulation class)
#implementation CCViewGeometry
+ (void)adjustView:(UIView *)view toSize:(CGSize)size
{
CGRect frame = view.frame;
frame.size = size;
view.frame = frame;
}
#end
or the UIView category route
#implementation UIView (CCGeometry)
- (void)resize:(CGSize)size
{
CGRect frame = self.frame;
frame.size = size;
self.frame = frame;
}
#end
Style notes pertinent to code found on this page:
All method params should begin with a lower-case char.
setFoo: is used in #property synthesis & by convention your method name indicates setting a property named object to the value of object. You're setting the size, not the object itself.
Be explicit. Why have a method called setObject: when you know the general type of object being passed?
Width & height in UIKit are represented (rightly) by CGFloat, not NSInteger. Why pass width + height instead of CGSize anyway?
Try using class methods when state is not required. + is your friend. Don't fire up instances for every little thing (most singleton methods I see in ObjC code should be refactored as class methods).
The programmers that don't care about the little things end up with unmaintainable code—code that will slow them and those that come after them down. Convention and style matter a lot on any decent-sized project.
Is is possible to stop CATiledLayer to draw (drawLayer:inContext)?
It draws asynchronously and when i try to release CGPDFDocumentRef, which is used by CATiledLayer, the app crashes (EXC_BAD_ACCESS).
That's my view:
#implementation TiledPDFView
- (id)initWithFrame:(CGRect)frame andScale:(CGFloat)scale{
if ((self = [super initWithFrame:frame])) {
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
tiledLayer.levelsOfDetail = 4;
tiledLayer.levelsOfDetailBias = 4;
tiledLayer.tileSize = CGSizeMake(512.0, 512.0);
myScale = scale;
}
return self;
}
// Set the layer's class to be CATiledLayer.
+ (Class)layerClass {
return [CATiledLayer class];
}
- (void)stopDrawing{
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
[tiledLayer removeFromSuperlayer];
tiledLayer.delegate = nil;
}
// Set the CGPDFPageRef for the view.
- (void)setPage:(CGPDFPageRef)newPage
{
CGPDFPageRelease(self->pdfPage);
self->pdfPage = CGPDFPageRetain(newPage);
//self->pdfPage = newPage;
}
-(void)drawRect:(CGRect)r
{
}
// Draw the CGPDFPageRef into the layer at the correct scale.
-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
// First fill the background with white.
CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
CGContextFillRect(context,self.bounds);
CGContextSaveGState(context);
// Flip the context so that the PDF page is rendered
// right side up.
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Scale the context so that the PDF page is rendered
// at the correct size for the zoom level.
CGContextScaleCTM(context, myScale,myScale);
CGContextDrawPDFPage(context, pdfPage);
CGContextRestoreGState(context);
}
// Clean up.
- (void)dealloc {
CGPDFPageRelease(pdfPage);
[super dealloc];
}
And this is where i try to stop and release PDF in view controller:
v is instance of TiledPDFView
-(void) stopDwaring {
[v stopDrawing];
[v removeFromSuperview];
[v release];
[self.view removeFromSuperview];
self.view = nil;
CGPDFDocumentRelease(pdf);
}
this post helped me solving my own trouble with CATiledLayer. I used TiledPDFview.m from Apple's documentation as example.
Since I need to redraw the entire view and all tiles at some point, I use a CATiledLayer as property.
When exiting and deallocating the viewcontroller, it crashed with [CATiledLayer retain]: Message sent to deallocated instance.
Here is my dealloc method of the view controller:
- (void)dealloc {
self.tiledLayer.contents=nil;
self.tiledLayer.delegate=nil;
[self.tiledLayer removeFromSuperlayer];
// note: releasing the layer still crashes-
// I guess removeFromSuperlayer releases it already,
// but couldn't find documentation so far.
// So that's why it's commented out:
// [self.tiledLayer release], self.tiledLayer=nil;
//release the other viewcontroller stuff...
[super dealloc];
}
That works for me. Hope it helps someone.
Remove the CATiledLayer from its superlayer before releasing the CGPDFDocumentRef.
[yourTiledLayer removeFromSuperlayer];
Dont forget to set it's delegate to nil too.
yourTiledLayer.delegate = nil;
After that, you can safely release your CGPDFDocumentRef.
Edit after OP adds code:
Did you get pdfPage using CGPDFDocumentGetPage()? If so, you shouldn't release it, it is an autoreleased object.
Regarding how to add it as sublayer:
You don't actually need TiledPDFView. In your view controller, you can simply do this:
CATiledLayer *tiledLayer = [CATiledLayer layer];
tiledLayer.delegate = self; //sets where tiledLayer will look for drawLayer:inContext:
tiledLayer.tileSize = CGSizeMake(512.0f, 512.0f);
tiledLayer.levelsOfDetail = 4;
tiledLayer.levelsOfDetailBias = 4;
tiledLayer.frame = CGRectIntegral(CGRectMake(0.0f, 0.0f, 512.0f, 512.0f));
[self.view.layer addSublayer:tiledLayer];
Then move your drawLayer:inContext: implementation to your view controller.
Then in your view controller's dealloc, release it as:
[tiledLayer removeFromSuperlayer];
tiledLayer.delegate = nil;
CGPDFDocumentRelease(pdf);
Note that you can't do this on a UIView subclass, as the drawLayer:inContext: will conflict with the UIView's main layer.
object.layer.contents = Nil
This should wait for the thread to finish. It helped in my case.
TiledPDFView *pdfView;
In dealloc of pdfView's superview class, write below line of codes.
- (void)dealloc {
if (nil != self.pdfView.superview) {
self.pdfView.layer.delegate = nil;
[self.pdfView removeFromSuperview];
}
}
This works for me. Hope it will help.
I've had a similar problem.
I ended up setting a float variable "zoom" in my TiledPDFView which I set as the zoomScale of PDFScrollView in the UIScrollview Delegate method: scrollViewDidZoom
Then in my drawLayer method inside TiledPDFView I only called the contents of that method if the float variable "zoom" was above 2.
This fixes any issues of someone leaving the view without zooming. It may not be ideal for your case as this error still occurs if someone zooms above 2 then releases the viewcontroller quickly, but you might be able to find a similar technique to cover all bases.
It looks like you're doing the same thing I am, which is borrowing the ZoomingPDFView code and integrating it into your project. If your UIScrollView delegate methods in PDFScrollView are unchanged, you can solve your problem by just commenting both lines of your dealloc method (in TiledPDFView). That stuff should only be happening when you kill the parent view, anyway.