Add UIView (from xib) with transparency to SceneKit - swift

I'm trying to load a UIView into SceneKit with a translucent background, but currently, it just fades to white as I decrease the opacity.
I have a very simple UIView layout in a .xib file that I want to load into SceneKit.
So far I can display the UIView in the SCNMaterial, change any text fields, images, etc inside the view without a problem. However, I cannot let it have transparency. If I change the alpha of the view it just fades to white.
most of the code is the below:
if let cardView = Bundle.main.loadNibNamed("OverlayCard", owner: self, options: nil)?.first as? OverlayCard {
cardView.backgroundColor = UIColor(displayP3Red: 1.0, green: 0.4, blue: 0.9, alpha: 0.2)
let newplane = SCNPlane()
let newMaterial = SCNMaterial()
cardView.alpha = 0.2
cardView.isOpaque = false
newMaterial.diffuse.contents = cardView
newMaterial.blendMode = .add
newplane.materials = [newMaterial]
let viewNode = SCNNode(geometry: newplane)
self.addChildNode(viewNode)
}
I've left in various things like assigning the blendMode, backgroundColor with 0.2 opacity as well as the entire view because these are all things I've tried but still get a white background with some of the view's elements on top of the white very faded out. Have also tried blendMode = .alpha to find no difference.
'self' here is a subclass of SCNNode.
Does anyone know how I can make this view's background fade to transparent, rather than fade to white? Or, another way to load a view into SceneKit.

Try setting the layer of the view as diffuse.contents instead of the view itself.
newMaterial.diffuse.contents = cardView.layer

Related

How do I add a background to my scene? SpriteKit Swift 4

I am making a endless runner game using SpriteKit. When I run the game, the background is black, and I've tried 3 methods on how to change the background, but it still is black. The code for my scene is
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
let scene = GameScene(size: view.frame.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
I want to replace the black background with this:
Does anyone know how to solve this?
In your GameScene.swift file, put the following code in your didMove(to:) method:
self.backgroundColor = UIColor(red: 199.0/255.0, green: 246.0/255.0, blue: 248.0/255.0, alpha: 1.0)
Also make sure that you are not already setting the background color in some other way.
If you have an image you want to make be your background, make a SKSpriteNode object with parameters that take in the name of your image within your assets.xcassets folder.
If your background doesnt fill the whole screen, use the setScale(multiplier) function on it to enlarge it. Resolution might be affected depending on how high resolution your image is/how large it is to start with.
Then add the SKSpriteNode with the addChild(SKSpriteNode) function. Ideally do this last part within your didMove(to:view) function.

shadow not behind text

I've made a function that is called in drawRect() inside a seperate made class for a Label. However, this draws only behind the text, and not behind the background of the label. I want to have a shadow behind the background of the label, not the text. How can I fix this? The same happens in a seperate made class for a View.
let COLOR_SHADOW_COLOR: CGColor = UIColor.gray.cgColor
let COLOR_SHADOW_OFFSET = CGSize(width: 2, height: -2)
let COLOR_SHADOW_RADIUS: CGFloat = 5
let COLOR_SHADOW_OPACITY: Float = 1.0
func setShadow(on object: UIView) {
object.layer.shadowColor = COLOR_SHADOW_COLOR
object.layer.shadowOpacity = COLOR_SHADOW_OPACITY
object.layer.shadowOffset = COLOR_SHADOW_OFFSET
object.layer.shadowRadius = COLOR_SHADOW_RADIUS
}
Fixed my own problem. The background was the same color as the text, but with a lower opacity. Xcode thinks that it should draw around text then. After using the pipet on the same color, it did made the shadow behind the label and view

NSCollectionViewItem's custom view flickers onReloadData

I have a NSCollectionViewItem with a custom view, in that view I am adding custom CALayer to some items. On button action, I am calling NSCollectionView's reloadData() method to reload the different data. This causes the custom CALayer to flicker as the item changes. Any idea how to fix it?
Adding CALayer to item's view based on some condition
circleLayer = CALayer();
let dimension = floor(min(bounds.width, bounds.height) - 10);
circleLayer.frame = NSMakeRect(4.5, 4, dimension, dimension);
circleLayer.cornerRadius = dimension / 2;
backgroundLayer.backgroundColor = NSColor(red: 84/255, green: 84/255, blue: 87/255, alpha: 1.0).cgColor;
circleLayer.masksToBounds = false;
circleLayer.isHidden = false;
layer?.addSublayer(circleLayer);
Function called on button click
//loading different data
collectionView.reloadData();
So as the button is clicked the background color of some other item changes, but the background color of previous item flickers. I guess this probably due to reuse of NSCOllectionViewItem. How can I avoid this? Any idea?

Borders not covering background

I've got a UILabel is using a border the same color as a background which it is half obscuring, to create a nice visual effect. However the problem is that there is still a tiny, yet noticeable, sliver of the label's background color on the OUTSIDE of the border.
The border is not covering the whole label!
Changing the border width doesn't change anything either, sadly.
Here's a picture of what's going on, enlarged so you can see it:
And my code follows:
iconLbl.frame = CGRectMake(theWidth/2-20, bottomView.frame.minY-20, 40, 40)
iconLbl.font = UIFont.fontAwesomeOfSize(23)
iconLbl.text = String.fontAwesomeIconWithName(.Info)
iconLbl.layer.masksToBounds = true
iconLbl.layer.cornerRadius = iconLbl.frame.size.width/2
iconLbl.layer.borderWidth = 5
iconLbl.layer.borderColor = topBackgroundColor.CGColor
iconLbl.backgroundColor = UIColor.cyanColor()
iconLbl.textColor = UIColor.whiteColor()
Is there something I'm missing?
Or am I going to have to figure out another to achieve this effect?
Thanks!
EDIT:
List of things I've tried so far!
Changing layer.borderWidth
Fussing around with clipsToBounds/MasksToBounds
Playing around the the layer.frame
Playing around with an integral frame
EDIT 2:
No fix was found! I used a workaround by extending this method on to my UIViewController
func makeFakeBorder(inputView:UIView,width:CGFloat,color:UIColor) -> UIView {
let fakeBorder = UIView()
fakeBorder.frame = CGRectMake(inputView.frame.origin.x-width, inputView.frame.origin.y-width, inputView.frame.size.width+width*2, inputView.frame.size.height+width*2)
fakeBorder.backgroundColor = color
fakeBorder.clipsToBounds = true
fakeBorder.layer.cornerRadius = fakeBorder.frame.size.width/2
fakeBorder.addSubview(inputView)
inputView.center = CGPointMake(fakeBorder.frame.size.width/2, fakeBorder.frame.size.height/2)
return fakeBorder
}
I believe this is the way a border is drawn to a layer in iOS. In the document it says:
When this value is greater than 0.0, the layer draws a border using the current borderColor value. The border is drawn inset from the receiver’s bounds by the value specified in this property. It is composited above the receiver’s contents and sublayers and includes the effects of the cornerRadius property.
One way to fix this is to apply a mask to a view's layer, but I found out that even if so we still can see a teeny tiny line around the view when doing snapshot tests. So to fix it more, I put this code to layoutSubviews
class MyView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
let maskInset: CGFloat = 1
// Extends the layer's frame.
layer.frame = layer.frame.inset(dx: -maskInset, dy: -maskInset)
// Increase the border width
layer.borderWidth = layer.borderWidth + maskInset
layer.cornerRadius = bounds.height / 2
layer.maskToBounds = true
// Create a circle shape layer with true bounds.
let mask = CAShapeLayer()
mask.path = UIBezierPath(ovalIn: bounds.inset(dx: maskInset, dy: maskInset)).cgPath
layer.mask = mask
}
}
CALayer's mask

UIView transparent gradient

Given an arbitrary UIView on iOS, is there a way using Core Graphics (CAGradientLayer comes to mind) to apply a "foreground-transparent" gradient to it?
I can't use a standard CAGradientLayer because the background is more complex than a UIColor. I also can't overlay a PNG because the background will change as my subview is scrolled along its parent vertical scrollview (see image).
I have a non-elegant fallback: have my uiview clip its subviews and move a pre-rendered gradient png of the background as the parent scrollview is scrolled.
This was an embarrassingly easy fix: apply a CAGradientLayer as my subview's mask.
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = _fileTypeScrollView.bounds;
gradientLayer.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor, (id)[UIColor clearColor].CGColor, nil];
gradientLayer.startPoint = CGPointMake(0.8f, 1.0f);
gradientLayer.endPoint = CGPointMake(1.0f, 1.0f);
_fileTypeScrollView.layer.mask = gradientLayer;
Thanks to Cocoanetics for pointing me in the right direction!
This is how I'll do.
Step 1 Define a custom gradient view (Swift 4):
import UIKit
class GradientView: UIView {
override open class var layerClass: AnyClass {
return CAGradientLayer.classForCoder()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let gradientLayer = self.layer as! CAGradientLayer
gradientLayer.colors = [
UIColor.white.cgColor,
UIColor.init(white: 1, alpha: 0).cgColor
]
backgroundColor = UIColor.clear
}
}
Step 2 - Drag and drop a UIView in your storyboard and set its custom class to GradientView
As an example, this is how the above gradient view looks like:
https://github.com/yzhong52/GradientViewDemo
I used the accepted (OP's) answer above and ran into the same issue noted in an upvoted comment - when the view scrolls, everything that started offscreen is now transparent, covered by the mask.
The solution was to add the gradient layer as the superview's mask, not the scroll view's mask. In my case, I'm using a text view, which is contained inside a view called contentView.
I added a third color and used locations instead of startPoint and endPoint, so that items below the text view are still visible.
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.contentView!.bounds
gradientLayer.colors = [UIColor.white.cgColor, UIColor.clear.cgColor, UIColor.white.cgColor]
// choose position for gradient, aligned to bottom of text view
let bottomOffset = (self.textView!.frame.size.height + self.textView!.frame.origin.y + 5)/self.contentView!.bounds.size.height
let topOffset = bottomOffset - 0.1
let bottomCoordinate = NSNumber(value: Double(bottomOffset))
let topCoordinate = NSNumber(value: Double(topOffset))
gradientLayer.locations = [topCoordinate, bottomCoordinate, bottomCoordinate]
self.contentView!.layer.mask = gradientLayer
Before, the text that started offscreen was permanently invisible. With my modifications, scrolling works as expected, and the "Close" button is not covered by the mask.
I just ran into the same issue and wound up writing my own class. It seems like serious overkill, but it was the only way I could find to do gradients with transparency. You can see my writeup and code example here
It basically comes down to a custom UIView that creates two images. One is a solid color, the other is a gradient that is used as an image mask. From there I applied the resulting image to the uiview.layer.content.
I hope it helps,
Joe
I hate to say it, but I think that you are into the CUSTOM UIView land. I think that I would try to implement this in a custom UIView overiding the drawRect routine.
With this, you could have that view, place on top of your actual scrollview, and have your gradient view (if you will) "pass-on" all touch events (i.e. relinquish first responder).