iOS 7 iPad SpriteKit - Use irregular SKShapeNode as mask for SKCropNode - ios7

Does anyone know how to crop an image with spritekit for iOS using an irregular shape node? The problem is when I do an skcrop on it, the shape has 2 layers, and thus the cropping fails. To crop one must use a single layer. Any idea how to rasterize a shape first, before the scene is loaded? I've tried skeffectnode and shouldRasterize on it but that fails too, most likely because it contains 2 children as well, or the rasterization is occuring after the scene is loaded. I've also tried converting the shape to a texture, but that fails for the same reasons as the skeffectnode does. I've looked at other possible solutions on stack overflow, and none seem to work or are very limited to squares, so I'm thinking this is a bug that must exist only in iOS7, so please don't say this is a duplicate without letting me check out the duplicate first to make sure that it really is one.
Right now all signs point to not using an skshapenode with a fill to crop an image.

I basically kept wrapping it until it worked. End all be all solution seemed to be creating an SKEffectNode and adding my SKShapeNode as a child of SKEffectNode. That wasn't enough though. I also had to wrap the SKEffectNode in an SKSpriteNode. Then finally, add the SKSpriteNode as the maskNode of SKCropNode. See the code below. I have additional settings that may be helpful to the solution as well.
SKCropNode *cropNode = [[SKCropNode alloc] init];
SKEffectNode*rasterEffectNode = [SKEffectNode node];
[rasterEffectNode addChild:ballShape];
rasterEffectNode.shouldRasterize = true;
ballShape.lineWidth = 0;
ballShape.antialiased = true;
[ballShape setStrokeColor:[SKColor clearColor]];
ballShape.fillColor = [SKColor blackColor];
SKSpriteNode*spriteWrapperFix = [SKSpriteNode node];
[spriteWrapperFix addChild:rasterEffectNode];
[cropNode setMaskNode:spriteWrapperFix];
[cropNode addChild:sprite];
[container addChild:cropNode];

Related

Fill SKShapeNode with pattern image

I'm trying to fill a SKShapeNode with an Image/pattern but I'm still unsuccessfull.
Can you help me solving this or giving me an alternative? I want to create a collidable custom shape (from any SpriteKit kind) filled with a pattern image.
I've tried the following:
UIBezierPath *path = [[UIBezierPath alloc] init];
[path addArcWithCenter:CGPointMake(0.0, 0.0) radius:50.0 startAngle:0.0 endAngle:(M_PI*2.0) clockwise:YES];
SKShapeNode *shape = [[SKShapeNode alloc] init];
UIImage *patternImg = [UIImage imageNamed:#"pattern"];
shape.path = path.CGPath;
shape.fillColor = [[SKColor alloc] initWithCGColor:[[UIColor alloc] initWithPatternImage:patternImg].CGColor];
and also:
shape.fillColor = [[SKColor alloc] initWithPatternImage:[[UIImage alloc] initWithCGImage:[UIImage imageNamed:#"Basketball"].CGImage]];
This works (but it isn't what I'm looking for):
shape.fillColor = [SKColor redColor];
Thank you!
Starting from iOS 8.0 there is fillTexture property in the SKShapeNode.
i had the sample problem in my game, finally my solution was to add a SKSpriteNode as a child of the SKShapeNode and it worked fine.
SKSpriteNode* node = [[SKSpriteNode alloc] initWithImageNamed:#"bombIcon.png"];
node.name = #"bomb";
node.size = CGSizeMake(10, 10);
[self.bombNode addChild:node];
Where self.bombNode is a SKShapeNode.
Hope it helps
You could try to achieve that with SKCropNode. However, I've seen several questions here that SKShapeNode cannot act as maskNode for SKCropNode, but I haven't tested it myself. In this case you probably have to use SKSpriteNode instead of SKShapeNode.
Well, using modern Swift (you're using Swift by now, right?), you could try:
var marbleNode: SKSpriteNode!
Then later, in your init method:
marbleNode = SKSpriteNode(imageNamed: "SmallerSwirl");
marbleNode.physicsBody = SKPhysicsBody(circleOfRadius: 35.0)
marbleNode.physicsBody?.dynamic = true
marbleNode.physicsBody?.affectedByGravity = true
print(marbleNode.physicsBody)
marbleNode.position = CGPointMake(centerPoint.position.x + 10.0, centerPoint.position.y + 10.0)
self.addChild(marbleNode)
Okay, so that gives us a round sprite node to work with. The Sprite node is responsive to physics, because you set up its physics body separately. So far, so good. Now we need to address the glossed-over part, namely the introduction of the SmallerSwirl .png image.
When you set up your project, it included an Assets.xcassets (pronounced, "x c assets") entry. Click on it, then click on the "+" sign at the middle/bottom of the first column, by the word filter. From the menu that appears, select "New Image Set". A new entry labeled "Image" appears. Click on the word "Image" to change it to "SmallerSwirl".
Next to the SmallerSwirl entry, you see blanks labeled 1x, 2x, and 3x. They are for different screen resolutions. Start by dragging your preferred .png image into the 1x square. That image can be named whatever you want it to be named. It doesn't have to be named SmallerSwirl, though it can be. Drag other images to the 2x and 3x slots if you like.
Run, and you should see your preferred image embodied as a sprite, dancing around the screen.

Using the camera on the iPad/iPhone with an overlay

I am trying to add an overlay image to a photo that is taken. Has anyone seen any examples on how I can do this? I want to have a picture which is a transparent PNG file, and then allow the user to take a picture with the image in it.
Iulius is correct that this is essentially a duplicate question. However, just to rule out one issue-- would you like the user to be able to see the overlay while composing the shot? (i.e. if your app makes different hats appear on people's heads, do you want to show the hat floating in space while they take the photo?). If you want to learn more about that, you'll need to use the cameraOverlayView property of the imagePickerController, which lets you superimpose your own view(s) on the camera. There are questions on this topic already on SO, like this one: How to add a overlay view to the cameraview and save it
Update re: scaling-- LilMoke, I assume when you say that the image is offset you're getting into trouble with the difference with the camera's aspect ratio (4:3) and the screen of the iPhone (3:4). You can define a constant and use it to set the cameraViewTransform property of your UIImagePickerController. Here's a code snippet, partially borrowed, and simplified from the excellent augmented reality tutorial at raywenderlich.com:
#define CAMERA_TRANSFORM 1.24299
// First create an overlay view for your superimposed image
overlay = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
overlay.backgroundColor=[UIColor clearColor];
overlay.opaque = NO;
UIImagePickerController *imagePicker;
imagePicker = [[[UIImagePickerController alloc] init] autorelease];
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePicker.showsCameraControls = YES; // assuming you need these?
imagePicker.toolbarHidden = YES;
imagePicker.navigationBarHidden = YES;
imagePicker.wantsFullScreenLayout = YES;
imagePicker.cameraViewTransform = CGAffineTransformScale(imagePicker.cameraViewTransform,
CAMERA_TRANSFORM, CAMERA_TRANSFORM); // If I understood your problem, this should help
imagePicker.cameraOverlayView = overlay;
If code along these lines doesn't get you on track, then maybe you can post all the relevant code from your troubled project here. Hopefully it's just a matter of setting the cameraViewTransform as I said above.

How to flip a UIView around the x-axis while simultaneously switching subviews

This question has been asked before but in a slightly different way and I was unable to get any of the answers to work the way I wanted, so I am hoping somebody with great Core Animation skills can help me out.
I have a set of cards on a table. As the user swipes up or down the set of cards move up and down the table. There are 4 cards visible on the screen at any given time, but only the second card is showing its face. As the user swipes the second card flips back onto its face and the next card (depending on the swipe direction) lands in it's place showing its face.
I have set up my card view class like this:
#interface WLCard : UIView {
UIView *_frontView;
UIView *_backView;
BOOL flipped;
}
And I have tried flipping the card using this piece of code:
- (void) flipCard {
[self.flipTimer invalidate];
if (flipped){
return;
}
id animationsBlock = ^{
self.backView.alpha = 1.0f;
self.frontView.alpha = 0.0f;
[self bringSubviewToFront:self.frontView];
flipped = YES;
CALayer *layer = self.layer;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / 500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, M_PI, 1.0f, 0.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;
};
[UIView animateWithDuration:0.25
delay:0.0
options: UIViewAnimationCurveEaseInOut
animations:animationsBlock
completion:nil];
}
This code works but it has the following problems with it that I can't seem to figure out:
Only half of the card across the x-axis is animated.
Once flipped, the face of the card is upside down and mirrored.
Once I've flipped the card I cannot get the animation to ever run again. In other words, I can run the animation block as many times as I want, but only the first time will animate. The subsequent times I try to animate lead to just a fade in and out between the subviews.
Also, bear in mind that I need to be able to interact with the face of the card. i.e. it has buttons on it.
If anybody has run into these issues it would be great to see your solutions. Even better would be to add a perspective transform to the animation to give it that extra bit of realism.
This turned out to be way simpler than I thought and I didn't have to use any CoreAnimation libraries to achieve the effect. Thanks to #Aaron Hayman for the clue. I used transitionWithView:duration:options:animations:completion
My implementation inside the container view:
[UIView transitionWithView:self
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromBottom
animations: ^{
[self.backView removeFromSuperview];
[self addSubview:self.frontView];
}
completion:NULL];
The trick was the UIViewAnimationOptionTransitionFlipFromBottom option. Incidentally, Apple has this exact bit of code in their documentation. You can also add other animations to the block like resizing and moving.
Ok, this won't be a complete solution but I'll point out some things that might be helpful. I'm not a Core-Animation guru but I have done a few 3D rotations in my program.
First, there is no 'back' to a view. So if you rotate something by M_PI (180 degrees) you're going to be looking at that view as though from the back (which is why it's upside down/mirrored).
I'm not sure what you mean by:
Only half of the card across the x-axis is animated.
But, it it might help to consider your anchor point (the point at which the rotation occurs). It's usually in the center, but often you need it to be otherwise. Note that anchor points are expressed as a proportion (percentage / 100)...so the values are 0 - 1.0f. You only need to set it once (unless you need it to change). Here's how you access the anchor point:
layer.anchorPoint = CGPointMake(0.5f, 0.5f) //This is center
The reason the animation only ever runs once is because transforms are absolute, not cumulative. Consider that you're always starting with the identity transform and then modifying that, and it'll make sense...but basically, no animation occurs because there's nothing to animate the second time (the view is already in the state you're requesting it to be in).
If you're animating from one view to another (and you can't use [UIView transitionWithView:duration:options:animations:completion:];) you'l have to use a two-stage animation. In the first stage of the animation, for the 'card' that is being flipped to backside, you'll rotate the view-to-disappear 'up/down/whatever' to M_PI_2 (at which point it will be 'gone', or not visible, because of it's rotation). And in the second stage, you're rotate the backside-of-view-to-disappear to 0 (which should be the identity transform...aka, the view's normal state). In addition, you'll have to do the exact opposite for the 'card' that is appearing (to frontside). You can do this by implementing another [UIView animateWithDuration:...] in the completion block of the first one. I'll warn you though, doing this can get a little bit complicated. Especially since you're wanting views to have a 'backside', which will basically require animating 4 views (the view-to-disappear, the view-to-appear, backside-of-view-to-disappear, and the backside-of-view-to-appear). Finally, in the completion block of the second animation you can do some cleanup (reset view that are rotated and make their alpha 0.0f, etc...).
I know this is complicated, so you might want read some tutorial on Core-Animation.
#Aaron has some good info that you should read.
The simplest solution is to use a CATransformLayer that will allow you to place other CALayer's inside and maintain their 3D hierarchy.
For example to create a "Card" that has a front and back you could do something like this:
CATransformLayer *cardContainer = [CATransformLayer layer];
cardContainer.frame = // some frame;
CALayer *cardFront = [CALayer layer];
cardFront.frame = cardContainer.bounds;
cardFront.zPosition = 2; // Higher than the zPosition of the back of the card
cardFront.contents = (id)[UIImage imageNamed:#"cardFront"].CGImage;
[cardContainer addSublayer:cardFront];
CALayer *cardBack = [CALayer layer];
cardBack.frame = cardContainer.bounds;
cardBack.zPosition = 1;
cardBack.contents = (id)[UIImage imageNamed:#"cardBack"].CGImage; // You may need to mirror this image
[cardContainer addSublayer:cardBack];
With this you can now apply your transform to cardContainer and have a flipping card.
#Paul.s
I followed your approach with card container but when i applt the rotation animation on card container only one half of the first card rotates around itself and finally the whole view appears.Each time one side is missing in the animation
Based on Paul.s this is updated for Swift 3 and will flip a card diagonally:
func createLayers(){
transformationLayer = CATransformLayer(layer: CALayer())
transformationLayer.frame = CGRect(x: 15, y: 100, width: view.frame.width - 30, height: view.frame.width - 30)
let black = CALayer()
black.zPosition = 2
black.frame = transformationLayer.bounds
black.backgroundColor = UIColor.black.cgColor
transformationLayer.addSublayer(black)
let blue = CALayer()
blue.frame = transformationLayer.bounds
blue.zPosition = 1
blue.backgroundColor = UIColor.blue.cgColor
transformationLayer.addSublayer(blue)
let tgr = UITapGestureRecognizer(target: self, action: #selector(recTap))
view.addGestureRecognizer(tgr)
view.layer.addSublayer(transformationLayer)
}
Animate a full 360 but since the layers have different zPositions the different 'sides' of the layers will show
func recTap(){
let animation = CABasicAnimation(keyPath: "transform")
animation.delegate = self
animation.duration = 2.0
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
animation.toValue = NSValue(caTransform3D: CATransform3DMakeRotation(CGFloat(Float.pi), 1, -1, 0))
transformationLayer.add(animation, forKey: "arbitrarykey")
}

Why is CATransform3D not working when triggered from viewWillAppear?

In viewWillAppear I want to adjust some of my views using the following transformation (Monotouch code):
CATransform3D oTransform3D = CATransform3D.Identity;
oTransform3D.m34 = 1.0f / -400;
oTransform3D = oTransform3D.Translate( 110, 0, 0);
oTransform3D = oTransform3D.Rotate( (-70f) * (float)Math.PI / 180f, 0, 1, 0);
However, this causes the view to be rendered far left of the screen.
If I put the very same code in viewDidAppear, it is working.
I have already checked that all views have valid sizes.
Did you ever figure this out? I've run into a similar situation where a 3D transform refuses to behave when applied to an immediate subview of a UIViewController's root view. A workaround for me was to wrap the subview in another container UIView.
Something along these lines:
- (void)viewDidLoad {
[super viewDidLoad];
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height)];
container.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoResizingFlexibleHeight;
[self.view addSubview:container];
self.myCrazyFlippingSpinningView = [[CrazyFlippingSpinningView alloc] init];
[container addSubview:self.myCrazyFlippingSpinningView];
}
Of course, I'd prefer to not have to do that. It's such a hack. If anyone's encountered this and has a better solution, I'd love to see it.
Thanks!
Not sure I fully understand the question but I am going to take a swag.
CATransform3D has a vanishing point that transforms refer to. The vanishing point is always the x,y center point of whatever view you place the transformed view onto. When I was struggling through the 3D transform issues and getting unexpected results, it was always solved by simply making sure I knew the size and center point of the container view.
You say your code works in ViewDidLoad...does that mean you are adding a transformed view to the base view? like: this.View.AddSubview(yourTransformedView); ??
I mean, I don't think your problem is a matter of ViewWillAppear vs. ViewDidLoad; I am going to guess that if I saw the code where you apply your transform to the views and then add your views to a base view or some other superview, I would be able to quickly tell you what is happening.

Drawing a layer shadow to a PDF context

I have a bunch of UIViews to which I add shadows via their layers, in their drawRect method:
self.layer.shadowPath = path;
self.layer.shadowColor = [[UIColor blackColor] CGColor];
self.layer.shadowOpacity = .6;
self.layer.shadowOffset = CGSizeMake(2,3);
self.layer.shadowRadius = 2;
This works well, but my problem is I also need to create a PDF with those views.
I'm doing this by creating a PDF context and passing it to the drawing method so that the drawing happens in the PDF context.
That also works well, except that the shadows are not rendered in the PDF. I've experimented with a couple approaches, but haven't managed to find a proper, easy way to get those shadows to appear where they belong in the PDF.
Would anyone know how to do this?
You will need to make the relevant CoreGraphics calls in the drawrect to draw the shadows rather than using the CALayer properties.
Check out the Apple docs on shadows.