How to add label to SCNNode? - sprite-kit

I’m trying to add a label to an SCNNode. So far I’ve managed to set a SKLabelNode as the material for the SCNNode. It sort of works, I can the SCNNode becomes the background colour of the SKLabelNode but I can’t see the text. Sometime I can see a red haze (the text colour is red) but no readable text.
I also tried setting the material as a UIView and adding a UiLabel as a sub view. Again it sets as I can the whole SCNNode becomes the background colour of the UiLabel but I can’t see any text.
var block = SCNNode()
var spriteScene = SKScene()
var Lbl = SKLabelNode()
lbl.text = “Hello World”
lbl.color = blue (playground colour literal)
lbl. = 2 //I tried various numbers
lbl.fontColor = SKColor.red
spriteScene.addChild(lbl)

I got it after some hit and trial. I had to try different values before I got these size, scale and rotation to display the label as I want.
note: My node here is SCNPlane with 0.3 width and 0.2 height, so size of SKScene and rectangle and position of label are hard coded accordingly.
func addLabel(text: String){
let sk = SKScene(size: CGSize(width: 3000, height: 2000))
sk.backgroundColor = UIColor.clear
let rectangle = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 3000, height: 2000), cornerRadius: 10)
rectangle.fillColor = UIColor.black
rectangle.strokeColor = UIColor.white
rectangle.lineWidth = 5
rectangle.alpha = 0.5
let lbl = SKLabelNode(text: text)
lbl.fontSize = 160
lbl.numberOfLines = 0
lbl.fontColor = UIColor.white
lbl.fontName = "Helvetica-Bold"
lbl.position = CGPoint(x:1500,y:1000)
lbl.preferredMaxLayoutWidth = 2900
lbl.horizontalAlignmentMode = .center
lbl.verticalAlignmentMode = .center
lbl.zRotation = .pi
sk.addChild(rectangle)
sk.addChild(lbl)
let material = SCNMaterial()
material.isDoubleSided = true
material.diffuse.contents = sk
node.geometry?.materials = [material]
node.geometry?.firstMaterial?.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(1), Float(1), 1)
node.geometry?.firstMaterial?.diffuse.wrapS = .repeat
node.geometry?.firstMaterial?.diffuse.wrapS = .repeat
}

Related

shadow on bezierpath view adds weird strokes to corners

I wanted to recreate the AppStore's "today" cards with rounded corners and a light drop shadow.
I created a path, a maskLayer and a separate shadowLayer, which – according to several sources – is the way of doing it.
The problem, however, is that my lovely rounded rectangle with a shadow has got some gray strokes at it corners. How can I solve this? I tried different shadow opacities and different radii. It didn't solve my problem.
Here you can see my screenshots and my code below.
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
// create sample view and add to view hierarchy
let bigTeaser = UIView(frame: CGRect(x: 16, y: 200, width: 343, height: 267))
bigTeaser.backgroundColor = UIColor.white
view.addSubview(bigTeaser)
// create the path for the rounded corners and the shadow
let roundPath = UIBezierPath(roundedRect: bigTeaser.bounds, cornerRadius: 20)
// create maskLayer
let maskLayer = CAShapeLayer()
maskLayer.frame = bigTeaser.bounds
maskLayer.path = roundPath.cgPath
bigTeaser.layer.mask = maskLayer
// create shadowLayer
let shadowLayer = CAShapeLayer()
shadowLayer.path = roundPath.cgPath
shadowLayer.frame = bigTeaser.frame
shadowLayer.shadowOpacity = 0.3
shadowLayer.shadowRadius = 24
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowOffset = CGSize(width: 0, height: 2)
// insert layers
bigTeaser.superview!.layer.insertSublayer(shadowLayer, below: bigTeaser.layer)
}
If you replace:
shadowLayer.path = roundPath.cgPath
to
shadowLayer.shadowPath = roundPath.cgPath
The ugly borders will magically disappear.

UILabel with Border and background Issue

Here is the code I have for UILabel
descriptionText.layer.cornerRadius = 8
descriptionText.layer.borderColor = UIColor.lightGray.cgColor
descriptionText.layer.borderWidth = 2
descriptionText.text = ""
descriptionText.layer.shadowOffset = CGSize(width: -10, height: -10)
descriptionText.layer.shadowRadius = -5.0
descriptionText.backgroundColor = .white
descriptionText.textColor = .black
descriptionText.numberOfLines = 0
descriptionText.lineBreakMode = .byWordWrapping
However This is what it looks like
Why is the Corner Not Transparent or how do you make what is outside the border transparent
Thanks in advance
You need to hide everything outside of the cornerRadius by setting .clipsToBounds on the label to true. Problem is that if you do this you will lose the shadow, as this is outside the bounds as well. Try this:
Place your label inside a container view, constrain the label to the container edges, and create an outlet to it. Then try the following code:
containerView.borderColor = UIColor.lightGray.cgColor
containerView.layer.borderColor = UIColor.lightGray.cgColor
containerView.layer.borderWidth = 2
containerView.layer.shadowOffset = CGSize(width: -10, height: -10)
containerView.layer.shadowRadius = -5.0
containerView.backgroundColor = UIColor.clear
descriptionText.text = "Testing"
descriptionText.cornerRadius = 8
descriptionText.clipsToBounds = true // or descriptionText.layer.masksToBounds = true
descriptionText.backgroundColor = .white
descriptionText.textColor = .black
descriptionText.numberOfLines = 0
descriptionText.lineBreakMode = .byWordWrapping

How to make a UIView not see through when the parent view has alpha value of 0.5 - SWIFT

I am trying to get a subview to not be see through when its parent view has an alpha value of 0.5. My code is below:
// Background
let popUpBackground = UIView.init(frame: self.view.frame)
popUpBackground.backgroundColor = UIColor.lightGray
popUpBackground.alpha = 0.5
// Popup
var popUp = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 100))
popUp.backgroundColor = UIColor.blue
popUp.alpha = 1.0 // This view appears to inherit the parents alpha value
// Add popUp as subview to popUpBackground
popUpBackground.addSubview(popUp)
self.navigationController?.view.addSubview(popUpBackground)
you can just change background color alpha
with UIColor.white.withAlphaComponent(alphaValue)
so update popUpBackground code to
let popUpBackground = UIView.init(frame: self.view.frame)
popUpBackground.backgroundColor = UIColor.white.withAlphaComponent(0.3)
// popUpBackground.alpha = 0.1
self.view.addSubview(popUpBackground)
Do not set the alpha of parentView, rather set the alpha to background color of parentView.
So rather:
let popUpBackground = UIView.init(frame: self.view.frame)
popUpBackground.backgroundColor = UIColor.lightGray
popUpBackground.alpha = 0.5
Use:
let popUpBackground = UIView.init(frame: self.view.frame)
popUpBackground.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)

Crop/Mask circular image node in sprite kit gives jagged edges

Is it possible give a circular mask/crop to an image node without jagged edges?
Following this example from Apple (https://developer.apple.com/reference/spritekit/skcropnode), the result is not ideal. You can click on the link to see.
let shapeNode = SKShapeNode()
shapeNode.physicsBody = SKPhysicsBody(circleOfRadius: radius)
shapeNode.physicsBody?.allowsRotation = false
shapeNode.strokeColor = SKColor.clearColor()
// Add a crop node to mask the profile image
// profile images (start off with place holder)
let scale = 1.0
let profileImageNode = SKSpriteNode(imageNamed: "PlaceholderUser")
profileImageNode.setScale(CGFloat(scale))
let circlePath = CGPathCreateWithEllipseInRect(CGRectMake(-radius, -radius, radius*2, radius*2), nil)
let circleMaskNode = SKShapeNode()
circleMaskNode.path = circlePath
circleMaskNode.zPosition = 12
circleMaskNode.name = "connection_node"
circleMaskNode.fillColor = SKColor.whiteColor()
circleMaskNode.strokeColor = SKColor.clearColor()
let zoom = SKAction.fadeInWithDuration(0.25)
circleMaskNode.runAction(zoom)
let cropNode = SKCropNode()
cropNode.maskNode = circleMaskNode
cropNode.addChild(profileImageNode)
cropNode.position = shapeNode.position
shapeNode.addChild(cropNode)
self.addChild(shapeNode)
UPDATE:
Ok, so here's one solution I came up. Not super ideal but it works perfectly. Essentially, I have a to size/scale, and cut the image exactly the way it would go on the SKSpriteNode so I would not have to use SKCropNode or some variation of SKShapeNode.
I used these UIImage extensions by Leo Dabus to resize/shape the image exactly as needed. Cut a UIImage into a circle Swift(iOS)
var circle: UIImage? {
let square = CGSize(width: min(size.width, size.height), height: min(size.width, size.height))
let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: square))
imageView.contentMode = .ScaleAspectFill
imageView.image = self
imageView.layer.cornerRadius = square.width/2
imageView.layer.masksToBounds = true
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
imageView.layer.renderInContext(context)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result
}
func resizedImageWithinRect(rectSize: CGSize) -> UIImage {
let widthFactor = size.width / rectSize.width
let heightFactor = size.height / rectSize.height
var resizeFactor = widthFactor
if size.height > size.width {
resizeFactor = heightFactor
}
let newSize = CGSizeMake(size.width/resizeFactor, size.height/resizeFactor)
let resized = resizedImage(newSize)
return resized
}
The final codes look like this:
//create/shape image
let image = UIImage(named: "TestImage")
let scaledImage = image?.resizedImageWithinRect(CGSize(width: 100, height: 100))
let circleImage = scaledImage?.circle
//create sprite
let sprite = SKSpriteNode(texture: SKTexture(image: circleImage!))
sprite.position = CGPoint(x: view.frame.width/2, y: view.frame.height/2)
//set texture/image
sprite.texture = SKTexture(image: circleImage!)
sprite.physicsBody = SKPhysicsBody(texture: SKTexture(image: circleImage!), size: CGSizeMake(100, 100))
if let physics = sprite.physicsBody {
//add the physic properties
}
//scale node
sprite.setScale(1.0)
addChild(sprite)
So if you have a perfectly scaled asset/image, then you probably dont need to do all this work, but I'm getting images from the backend that could come in any sizes.
There are two different techniques that can be combined to reduce the aliasing of edges created from cropping.
Create bigger images than you need, and then scale them down. Both the target (to be cropped) and the mask. Perform the cropping action, then scale down to required size.
Use very subtle blurring of the cropping shape, to soften its edges. This is best done in Photoshop or a similar editing program, to taste and need.
When these two techniques are combined, the results can be very good.
Let the stroke color to be displayed. Also, you can make line width a little thicker and the jagged edges will dissapear.
circleMaskNode.strokeColor = SKColor.whiteColor()
All you have to do is change the SKShapeNode's lineWidth property to be twice the radius of the circle:
func circularCropNode(radius: CGFloat, add: SKNode) {
let cropper = SKCropNode.init()
cropper.addChild(add)
addChild(cropper)
let circleMask = SKShapeNode.init(circleOfRadius: radius/2)
circleMask.lineWidth = radius
cropper.maskNode = circleMask
}

CAShapeLayer - Circle within UIVIew

I am struggling to draw a circle within a UIView added onto my main view, using Swift 2.0 in Xcode 7.
The UIView has been created in storyboard and declared as an IBOutlet in code:
#IBOutlet var circleView: UIView!
I have used code from a tutorial online to draw a circle in time to a countdown timer:
func createCircle(){
let bounds = CGRect(x: 0, y: 0, width: circleView.frame.width / 2, height: circleView.frame.width / 2)
// Create CAShapeLayer
let rectShape = CAShapeLayer()
rectShape.bounds = bounds
rectShape.position = circleView.center
rectShape.cornerRadius = bounds.width / 2
view.layer.addSublayer(rectShape)
rectShape.path = UIBezierPath(ovalInRect: rectShape.bounds).CGPath
rectShape.lineWidth = (bounds.width) / 10 // chnage this to change line width
rectShape.strokeColor = UIColor.whiteColor().CGColor
rectShape.fillColor = UIColor.clearColor().CGColor
rectShape.strokeStart = 0
rectShape.strokeEnd = 0
let start = CABasicAnimation(keyPath: "strokeStart")
start.toValue = 0
let end = CABasicAnimation(keyPath: "strokeEnd")
end.toValue = 1
let group = CAAnimationGroup()
group.animations = [start, end]
group.duration = timerDuration
group.autoreverses = false // use true to bounce it back
group.repeatCount = 1 // can use the keyword HUGE to repeat forver
rectShape.addAnimation(group, forKey: nil)
}
I have been playing around with the position, but it never seems to center the circle in the center of circleView.
Here is code I use to make a user's profile image round:
imageView.layer.cornerRadius = imageView.frame.size.width / 2
imageView.clipsToBounds = true
In your case, you would use circleView instead of imageView.