iOS Core Animation: CALayer bringSublayerToFront? - iphone

How do I bring a CALayer sublayer to the front of all sublayers, analogous to -[UIView bringSubviewToFront]?

I'm curious why none of these answers mention the zPosition attribute on CALayer. Core Animation looks at this attribute to figure out layer rendering order. The higher the value, the closer it is to the front. These answers all work as long as your zPosition is 0, but to easily bring a layer to the front, set its zPosition value higher than all other sublayers.

This is variation of #MattDiPasquale's implementation which reflects UIView's logic more precisely:
- (void) bringSublayerToFront:(CALayer *)layer
{
[layer removeFromSuperlayer];
[self insertSublayer:layer atIndex:[self.sublayers count]];
}
- (void) sendSublayerToBack:(CALayer *)layer
{
[layer removeFromSuperlayer];
[self insertSublayer:layer atIndex:0];
}
Note: if you don't use ARC, you may wish to add [layer retain] at top and [layer release] at bottom of both functions to make sure layer is not accidentally destructed in a case it has retain count = 1.

Right code here
- (void)bringSublayerToFront:(CALayer *)layer {
CALayer *superlayer = layer.superlayer;
[layer removeFromSuperlayer];
[superlayer insertSublayer:layer atIndex:[superlayer.sublayers count]];
}
- (void)sendSublayerToBack:(CALayer *)layer {
CALayer *superlayer = layer.superlayer;
[layer removeFromSuperlayer];
[superlayer insertSublayer:layer atIndex:0];
}

Swift 4 version.
Idea that layer itself has bringToFront and sendToBack methods.
#if os(iOS)
import UIKit
#elseif os(OSX)
import AppKit
#endif
extension CALayer {
func bringToFront() {
guard let sLayer = superlayer else {
return
}
removeFromSuperlayer()
sLayer.insertSublayer(self, at: UInt32(sLayer.sublayers?.count ?? 0))
}
func sendToBack() {
guard let sLayer = superlayer else {
return
}
removeFromSuperlayer()
sLayer.insertSublayer(self, at: 0)
}
}
Usage:
let view = NSView(frame: ...)
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.gray.cgColor
let l1 = CALayer(...)
let l2 = CALayer(...)
view.layer?.addSublayer(l1)
view.layer?.addSublayer(l2)
l1.bringToFront()

Create a category of CALayer like this:
#interface CALayer (Utils)
- (void)bringSublayerToFront;
#end
#implementation CALayer (Utils)
- (void)bringSublayerToFront {
CGFloat maxZPosition = 0; // The higher the value, the closer it is to the front. By default is 0.
for (CALayer *layer in self.superlayer.sublayers) {
maxZPosition = (layer.zPosition > maxZPosition) ? layer.zPosition : maxZPosition;
}
self.zPosition = maxZPosition + 1;
}
#end

This is the correct code:
-(void)bringSubLayerToFront:(CALayer*)layer
{
[layer.superLayer addSubLayer:layer];
}
-(void)sendSubLayerToBack:(CALayer*)layer
{
[layer.superlayer insertSublayer:layer atIndex:0];
}

You can implement this functionality in a category on CALayer like so:
CALayer+Extension.h
#import <QuartzCore/QuartzCore.h>
typedef void (^ActionsBlock)(void);
#interface CALayer (Extension)
+ (void)performWithoutAnimation:(ActionsBlock)actionsWithoutAnimation;
- (void)bringSublayerToFront:(CALayer *)layer;
#end
CALayer+Extension.m
#import "CALayer+Extension.h"
#implementation CALayer (Extension)
+ (void)performWithoutAnimation:(ActionsBlock)actionsWithoutAnimation
{
if (actionsWithoutAnimation)
{
// Wrap actions in a transaction block to avoid implicit animations.
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
actionsWithoutAnimation();
[CATransaction commit];
}
}
- (void)bringSublayerToFront:(CALayer *)layer
{
// Bring to front only if already in this layer's hierarchy.
if ([layer superlayer] == self)
{
[CALayer performWithoutAnimation:^{
// Add 'layer' to the end of the receiver's sublayers array.
// If 'layer' already has a superlayer, it will be removed before being added.
[self addSublayer:layer];
}];
}
}
#end
And for easy access you can #import "CALayer+Extension.h" in your project's Prefix.pch (precompiled header) file.

I don't think such methods exist, but it's easy to roll your own.
// CALayer+Additions.h
#interface CALayer (Additions)
- (void)bringSublayerToFront:(CALayer *)layer;
- (void)sendSublayerToBack:(CALayer *)layer;
#end
// CALayer+Additions.m
#implementation CALayer (Additions)
- (void)bringSublayerToFront:(CALayer *)layer {
CALayer *superlayer = self.superlayer;
[self removeFromSuperlayer];
[superlayer insertSublayer:gradientLayer atIndex:[self.sublayers count]-1];
}
- (void)sendSublayerToBack:(CALayer *)layer {
CALayer *superlayer = self.superlayer;
[self removeFromSuperlayer];
[superlayer insertSublayer:gradientLayer atIndex:0];
}

Related

Rotate a sprite in cocos2d using accelerometer

I'm on cocos2d and I have a sprite that I would like to rotate with the accelerometer.
I've heard about CMMotionManager. I would like to know if it is possible to use it just for 2D rotation, and, if yes, how?
put this in onEnter:
UIAccelerometer *accelerometer = [UIAccelerometer sharedAccelerometer];
accelerometer.updateInterval = 1.0/50.0; //update interval in sec...so 1/50= 20 ms
accelerometer.delegate = self;
you need to conform to UIAccelerometerDelegate like so:
#interface MyClass:CCLayer <UIAccelerometerDelegate>
and implement this in MyClass.m:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
CCLOG(#"x = %f y = %f z = %f",acceleration.x,acceleration.y,acceleration.z);
mysprite.rotation=acceleration.x*20;
}
edit: almost forgot...put accelerometer.delegate = nil; in onExit
note that the method is called every time the accelerometer changes value..in all 3 vectors
cards on the table..i didnt use accelerometer ...ever..but it should look something like this...check the rotation property in the documentation and play a little with it
hope it helps
PS: loved the "sorry for my english i'm french" part...hilarious
edit: here is my test of that code..and made a few modifications..it works fairly smooth..if you dont like it play around with the values.
#import "cocos2d.h"
// HelloWorldLayer
UIAccelerationValue accelerationX;
UIAccelerationValue accelerationY;
float currentRawReading;
float calibrationOffset;
#interface HelloWorldLayer : CCLayer <UIAccelerometerDelegate>
{
CCLabelTTF *label;
}
// returns a CCScene that contains the HelloWorldLayer as the only child
+(CCScene *) scene;
#end
#import "HelloWorldLayer.h"
// HelloWorldLayer implementation
#implementation HelloWorldLayer
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
#define kFilteringFactor .05
CGFloat RadiansToDegrees(CGFloat radians) {return radians *180/M_PI;};
// on "init" you need to initialize your instance
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super" return value
if( (self=[super init])) {
UIAccelerometer *accel= [UIAccelerometer sharedAccelerometer];
accel.delegate=self;
accel.updateInterval=1/60;
// create and initialize a Label
label = [CCLabelTTF labelWithString:#"Hello World" fontName:#"Marker Felt" fontSize:64];
// ask director the the window size
CGSize size = [[CCDirector sharedDirector] winSize];
// position the label on the center of the screen
label.position = ccp( size.width /2 , size.height/2 );
label.flipY=YES; //i have absolutly no idea why the label is fliped :/
label.flipX=YES;
label.rotation=0;
// add the label as a child to this Layer
[self addChild: label];
}
return self;
}
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration{
CCLOG(#"acc called");
accelerationX=acceleration.x *kFilteringFactor +accelerationX *(1-kFilteringFactor);
accelerationY=acceleration.y*kFilteringFactor +accelerationY *(1-kFilteringFactor);
currentRawReading=atan2(accelerationY, accelerationX);
label.rotation=-RadiansToDegrees(currentRawReading);
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
[super dealloc];
}
#end

how to customize MKPolyLineView to draw different style lines

I want to customize the lines drawn on MKMapView to show a route so that the lines have a border color and a fill color. Similar to this where it has a black border and is filled with another color:
I'm currently just returning MKPolyLineView objects from mapView:viewForOverlay: which works fine for plain lines. The docs says the MKPolyLineView is not to be subclassed, so should I subclass MKOverlayView and implement my own drawMapRect? Or should I subclass MKOverlayPathView? Or create a replacement for MKPolylineView?
EDIT - what I'm asking is: where is the place to put your own Quartz drawing code in order to draw your own annotations/overlays? Currently I've created a subclass of MKOverlayView and implement my own drawMapRect:zoomScale:inContext: It's pretty easy to draw the overlay that way but is that the best solution?
You can do this by implementing your own MKOverlayPathView subclass, which draws the path twice in the map rect. Once thicker with black and once thinner on top with another colour.
I have created a simple drop-in replacement of MKPolylineView which lets you do that: ASPolylineView.
If you want to do it yourself, the two main methods that you need to implement could look like this:
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
UIColor *darker = [UIColor blackColor];
CGFloat baseWidth = self.lineWidth / zoomScale;
// draw the dark colour thicker
CGContextAddPath(context, self.path);
CGContextSetStrokeColorWithColor(context, darker.CGColor);
CGContextSetLineWidth(context, baseWidth * 1.5);
CGContextSetLineCap(context, self.lineCap);
CGContextStrokePath(context);
// now draw the stroke color with the regular width
CGContextAddPath(context, self.path);
CGContextSetStrokeColorWithColor(context, self.strokeColor.CGColor);
CGContextSetLineWidth(context, baseWidth);
CGContextSetLineCap(context, self.lineCap);
CGContextStrokePath(context);
[super drawMapRect:mapRect zoomScale:zoomScale inContext:context];
}
- (void)createPath
{
// turn the polyline into a path
CGMutablePathRef path = CGPathCreateMutable();
BOOL pathIsEmpty = YES;
for (int i = 0; i < self.polyline.pointCount; i++) {
CGPoint point = [self pointForMapPoint:self.polyline.points[i]];
if (pathIsEmpty) {
CGPathMoveToPoint(path, nil, point.x, point.y);
pathIsEmpty = NO;
} else {
CGPathAddLineToPoint(path, nil, point.x, point.y);
}
}
self.path = path;
}
You can just add two MKPolyLineView objects with the same coordinates, but different thicknesses.
Add one with a lineWidth of 10 (or whatever) with strokeColor set to black.
Then add another with a lineWidth of 6 with strokeColor set to your other desired color.
You can use the same MKPolyLine for both MKPolyLineView objects.
MKPolylineView can only be used for stroking a designated path. You can use some of the properties in MKOverlayPathView to change their appearance but only some of them would apply, e.g. fillColor, strokeColor.
If you want to draw something more complex, you can use MKOverlayPathView. It is more generic and thus suited for more than just stroking paths. For drawing simple lines, the result would be identical to MKPolylineView (at least, according to the docs).
If you want to do more complex drawing, subclass MKOverlayPathView. What you're trying to do is non-trivial.
I use a subclass NamedOverlay that holds an overlay an a name:
NamedOverlay.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface NamedOverlay : NSObject <MKOverlay>
#property (strong, readonly, nonatomic) NSString *name;
#property (strong, readonly, nonatomic) id<MKOverlay> overlay;
-(id)initWithOverlay:(id<MKOverlay>)overlay andName:(NSString *)name;
#end
NamedOverlay.m
#import "NamedOverlay.h"
#implementation NamedOverlay
- (id)initWithOverlay:(id<MKOverlay>)overlay andName:(NSString *)name
{
_name = name;
_overlay = overlay;
return self;
}
- (MKMapRect)boundingMapRect
{
return [_overlay boundingMapRect];
}
- (CLLocationCoordinate2D)coordinate
{
return [_overlay coordinate];
}
-(BOOL)intersectsMapRect:(MKMapRect)mapRect
{
return [_overlay intersectsMapRect:mapRect];
}
#end
and in the map controller I instantiate two overlays with different name, then in the MKMapViewDelegate I can identify which overlay I want to draw and do something like:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id < MKOverlay >)overlay
{
NamedOverlay *namedOverlay = (NamedOverlay *) overlay;
MKPolyline *polyline = namedOverlay.overlay;
if ([namedOverlay.name isEqualToString:#"top"]) {
MKPolylineView *view1 = [[MKPolylineView alloc] initWithOverlay:polyline];
view1.strokeColor = [UIColor whiteColor];
view1.lineWidth = 25.0;
return view1;
} else {
MKPolylineView *view1 = [[MKPolylineView alloc] initWithOverlay:polyline];
view1.strokeColor = [UIColor blueColor];
view1.lineWidth = 15.0;
return view1;
}
}
I know that this may not match the pure approach you want, but why not using MKPolygon instead of a MKPolyLine ?
Create a MKPolygon instance that represents a kind of corridor around your route, and then , when you create the MKPolygonView that corresponds to the MKPolygon/corridor you've created, set the properties of the MKPolygonView to get a different fill color and strokeColor
myPolygonView.lineWidth=3;
myPolygonView.fillColor=[UIColor blueColor];
myPolygonView.strokeColor=[UIColor darkGrayColor];
I didn't try it myself but this should work. Only drawback is that when you zoom in / out, the 'width' of the 'route' will change.... :/

Problem with pdf while opening in UIWebView

I have a problem while opening a pdf in a UIWebView. Zoom in and Zoom out doesn't work and even double tap doesn't enlarge the pdf font size.
Guys is there any way to do that....
If not can anyone share some code ....
#import
#interface TiledPDFView : UIView {
CGPDFPageRef pdfPage;
CGFloat myScale;
}
- (id)initWithFrame:(CGRect)frame andScale:(CGFloat)scale;
- (void)setPage:(CGPDFPageRef)newPage;
#end
#import "TiledPDFView.h"
#import
#implementation TiledPDFView
// Create a new TiledPDFView with the desired frame and scale.
- (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];
}
// Set the CGPDFPageRef for the view.
- (void)setPage:(CGPDFPageRef)newPage
{
CGPDFPageRelease(self->pdfPage);
self->pdfPage = CGPDFPageRetain(newPage);
}
-(void)drawRect:(CGRect)r
{
// UIView uses the existence of -drawRect: to determine if it should allow its CALayer
// to be invalidated, which would then lead to the layer creating a backing store and
// -drawLayer:inContext: being called.
// By implementing an empty -drawRect: method, we allow UIKit to continue to implement
// this logic, while doing our real drawing work inside of -drawLayer:inContext:
}
// 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,0.5);
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);
// 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];
}
#end
Add this to your project...
Hope it helped....
Make sure that «Multiple Touch» is enabled for the UIWebView.

Effect like Interface Builder connection lines on iPhone

Hi I'd like little bit of help on something. In my app, I have a UITableView which is populated with custom cells that represent images. (i.e. selecting a row displays a picture in an image view).
What I would like to do, is have an icon in the custom cell that I could drag to one of a series of image views. Similar to the way you can drag a line in IB to set connections. Once the user releases their finger I will have it check what part of the screen they released it and if it is one one of these rects that represent the picture frames, it will populate the picture frame with the image and the line will disappear.
I have never drawn lines in my app before so thats not something I know how to do (so im just looking for a link to a tutorial or class definition) and second, what problems will I have since the start point of the line is in a UITableViewCell and the end point is in the main view?
I have actually done this before, so I can give you the exact code :D
This is only the drawing part, you implement the touch events (in a separate class, otherwise remove the self.userInteractionEnabled = NO; in .m file
The BOOL dragged tells the LineView if the line should be drawn.
LineView.h
#import <UIKit/UIKit.h>
#interface LineView : UIView
{
CGPoint start;
CGPoint end;
BOOL dragged;
}
#property CGPoint start, end;
#property BOOL dragged;
#end
LineView.m
#import "LineView.h"
#implementation LineView
#synthesize start, end, dragged;
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame: frame])
{
self.userInteractionEnabled = NO;
self.backgroundColor = [UIColor clearColor];
}
return self;
}
#pragma mark Setters
-(void) setStart: (CGPoint) s
{
start = s;
[self setNeedsDisplay];
}
-(void) setEnd: (CGPoint) e
{
end = e;
[self setNeedsDisplay];
}
#define LINE_COLOR [UIColor redColor]
#define CIRCLE_COLOR [UIColor redColor]
#define LINE_WIDTH 5
#define CIRCLE_RADIUS 10
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
if(dragged) {
[LINE_COLOR setStroke];
CGContextMoveToPoint(c, start.x, start.y);
CGContextAddLineToPoint(c, end.x, end.y);
CGContextSetLineWidth(c, LINE_WIDTH);
CGContextClosePath(c);
CGContextStrokePath(c);
[CIRCLE_COLOR setFill];
CGContextAddArc(c, start.x, start.y, CIRCLE_RADIUS, 0, M_PI*2, YES);
CGContextClosePath(c);
CGContextFillPath(c);
CGContextAddArc(c, end.x, end.y, CIRCLE_RADIUS, 0, M_PI*2, YES);
CGContextClosePath(c);
CGContextFillPath(c);
}
}
- (void)dealloc
{
[super dealloc];
}
#end

how to change UITabbar selected color?

according to this post
for now, Is apple will also reject this code?
and how to implement what apple will approve?
#interface UITabBar (ColorExtensions)
- (void)recolorItemsWithColor:(UIColor *)color shadowColor:(UIColor *)shadowColor shadowOffset:(CGSize)shadowOffset shadowBlur:(CGFloat)shadowBlur;
#end
#interface UITabBarItem (Private)
#property(retain, nonatomic) UIImage *selectedImage;
- (void)_updateView;
#end
#implementation UITabBar (ColorExtensions)
- (void)recolorItemsWithColor:(UIColor *)color shadowColor:(UIColor *)shadowColor shadowOffset:(CGSize)shadowOffset shadowBlur:(CGFloat)shadowBlur
{
CGColorRef cgColor = [color CGColor];
CGColorRef cgShadowColor = [shadowColor CGColor];
for (UITabBarItem *item in [self items])
if ([item respondsToSelector:#selector(selectedImage)] &&
[item respondsToSelector:#selector(setSelectedImage:)] &&
[item respondsToSelector:#selector(_updateView)])
{
CGRect contextRect;
contextRect.origin.x = 0.0f;
contextRect.origin.y = 0.0f;
contextRect.size = [[item selectedImage] size];
// Retrieve source image and begin image context
UIImage *itemImage = [item image];
CGSize itemImageSize = [itemImage size];
CGPoint itemImagePosition;
itemImagePosition.x = ceilf((contextRect.size.width - itemImageSize.width) / 2);
itemImagePosition.y = ceilf((contextRect.size.height - itemImageSize.height) / 2);
UIGraphicsBeginImageContext(contextRect.size);
CGContextRef c = UIGraphicsGetCurrentContext();
// Setup shadow
CGContextSetShadowWithColor(c, shadowOffset, shadowBlur, cgShadowColor);
// Setup transparency layer and clip to mask
CGContextBeginTransparencyLayer(c, NULL);
CGContextScaleCTM(c, 1.0, -1.0);
CGContextClipToMask(c, CGRectMake(itemImagePosition.x, -itemImagePosition.y, itemImageSize.width, -itemImageSize.height), [itemImage CGImage]);
// Fill and end the transparency layer
CGContextSetFillColorWithColor(c, cgColor);
contextRect.size.height = -contextRect.size.height;
CGContextFillRect(c, contextRect);
CGContextEndTransparencyLayer(c);
// Set selected image and end context
[item setSelectedImage:UIGraphicsGetImageFromCurrentImageContext()];
UIGraphicsEndImageContext();
// Update the view
[item _updateView];
}
}
#end
Yes, Apple will reject an app if you use that code.
I just had an app rejected for using private API calls. Specifically "_updateView". And I used the exact same code as above.
(If other people say that their app got approved with the same code it's just because it wasn't checked for use of private APIs.)
[[UITabBar appearance] setSelectedImageTintColor:[UIColor whiteColor]];
I suggest instead of changing color why don't you use selected tabbaritem image,
like
In iOS 6 I have change the selected tabbatitem image like -
in tabbar controller delegate method
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
if([tabBarController selectedIndex] == 0)
{
[viewController.tabBarItem setFinishedSelectedImage:[UIImage imageNamed:#"selected.png"]withFinishedUnselectedImage:[UIImage imageNamed:#"unselect.png"]];
}
}
through this you can change your image.
Or you can use directly in your view controllers init(or ViewWillAppear) method, like
[viewController.tabBarItem setFinishedSelectedImage:[UIImage imageNamed:#"selected.png"]withFinishedUnselectedImage:[UIImage imageNamed:#"unselect.png"]];
for iOS 10 (or higher):
To set selected color just set:
let tabBarAppearace = UITabBar.appearance()
tabBarAppearace.tintColor = UIColor.nowYouBlue
Above will work for all iOS version currently supported, but to change unselected color:
if #available(iOS 10.0, *) {
tabBarAppearace.unselectedItemTintColor = UIColor.red
} else {
// Fallback on earlier versions
}
Above code will look like this on iOS 10.