Cannot use color transparency in drawRect - ios

I'm having trouble with transparency in drawRect. I have a custom UIView. If I draw a rect bezier path and fill it with gray the color will be solid without any transparency even though I have the alpha set to 0.6. :
import UIKit
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
let grayOverlay = UIBezierPath(rect: rect)
UIColor(red: 34, green: 211, blue: 121, alpha: 0.6).set()
grayOverlay.fill()
}
}
Am I missing something here?

Views have and -opaque property.
By default is set to true due to performance reasons. If the view is not opaque it will require transparent blending, that is more intensive.
By setting to false you agree to have transparency between your view and what it is displayed under.

Related

How to create an IBDesignable custom UIView from a UIBezierPath?

I want to shape a UIView and be able to see its shape in the Interface Builder, when I set the following class to my UIView it fails to build, I'm not sure where's the error.
#IBDesignable
class CircleExampleView: UIView {
override func layoutSubviews() {
setupMask()
}
func setupMask() {
let path = makePath()
// mask the whole view to that shape
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
private func makePath() -> UIBezierPath {
//// Oval Drawing
let ovalPath = UIBezierPath(ovalIn: CGRect(x: 11, y: 12, width: 30, height: 30))
UIColor.gray.setFill()
ovalPath.fill()
return ovalPath
}
}
A couple of observations:
You are calling setFill followed by fill, but you only do that if drawing into a graphics context (e.g., in draw(_:)). When using CAShapeLayer, you should instead set the fillColor of the CAShapeLayer.
You are using the CAShapeLayer to set the mask of your view. If your UIView doesn't have a discernible backgroundColor, you won't see anything.
If you set the background color of the view to, say, blue, as shown below, your mask will reveal that blue background wherever the mask allows it to (in the oval of your path).
You have implemented layoutSubviews. You generally would do that only if you were doing something here that was contingent upon the bounds of the view. For example, here's a rendition where the oval path is based upon the bounds of the view:
#IBDesignable
class CircleView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
setupMask()
}
private func setupMask() {
let mask = CAShapeLayer()
mask.path = path.cgPath
mask.fillColor = UIColor.gray.cgColor
layer.mask = mask
}
private var path: UIBezierPath {
return UIBezierPath(ovalIn: bounds)
}
}
As E. Coms said, if you override layoutSubviews, you really should call the super implementation. This isn't critical, as the default implementation actually does nothing, but it's best practice. E.g. if you later changed this class to subclass some other UIView subclass, you don't want to have to go to revisit all these overrides.
If you have a designable view, it's advisable to put that in a separate target. That way, the rendering of the view in the storyboard is not dependent upon any work that may be underway in the main project. As long as the designables target (often the name of your main target with Kit suffix) can build, the designable view can be rendered.
For example, here is a rendition of your designable view, in a separate framework target, and used in a storyboard where the view in question has a blue backgroundColor:
For what it's worth, I think it's exceedingly confusing to have to mask to reveal the background color inside the oval. An app developer has to set "background" color in order to set what's inside the oval, but not the background.
I might instead remove the "mask" logic and instead give the designable view an inspectable property, fillColor, and just add a CAShapeLayer as a sublayer, using that fillColor:
#IBDesignable
class CircleView: UIView {
private var shapeLayer = CAShapeLayer()
#IBInspectable var fillColor: UIColor = .blue {
didSet {
shapeLayer.fillColor = fillColor.cgColor
}
}
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
shapeLayer.fillColor = fillColor.cgColor
layer.addSublayer(shapeLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
}
}
This accomplishes the same thing, but I think the distinction of fill colors vs background colors is more intuitive. But you may have had other reasons for using the masking approach, but just make sure if you do that, that you have something to reveal after it’s masked (e.g. a background color or something else you’re rendering).
Please add "super.layoutSubviews()"
override func layoutSubviews() {
setupMask()
super.layoutSubviews()
}
By this way, your design view will be "up to date" .

ContainerView with shadow and rounded edges

I would like to create custom ContainerView with shadowed and rounded edges. This ContainerView is in form of small rectangle placed on the top of another UIView. In this peculiar situation neither additional layers nor drawing shadow using CoreGraphics are helpful.
You're wrong that additional views/layers won't help.
You can place roundedContainer with rounded corners into another shadowedView with shadow added to it's layer.
To avoid those white corners make sure you set background color to clear somewhere.
Example:
//superview for container with rounded corners
shadowedView.backgroundColor = UIColor.clear //this will fix your white corners issue
shadowedView.layer.shadowColor = UIColor.black.cgColor
shadowedView.layer.shadowOffset = .zero
shadowedView.layer.shadowOpacity = 0.3
shadowedView.layer.shadowRadius = 5.0
//add a container with rounded corners
let roundedView = UIView()
roundedView.frame = baseView.bounds
roundedView.layer.cornerRadius = 10
roundedView.layer.masksToBounds = true
shadowedView.addSubview(roundedView)
I found a proper solution. I dropped shadow to ContainerView which is a superclass for every UIView inside. Then, I rounded edges using UIViewController class for this small rectangle area.
class GraphViewController: UIViewController {
#IBOutlet var graphView: GraphViewRenderer!
override func viewDidLoad() {
graphView.layer.cornerRadius = 20.0
graphView.layer.masksToBounds = true
super.viewDidLoad()
}
}
class GraphContainerView: UIView {
func applyPlainShadow() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize.zero
layer.shadowRadius = 5.0
layer.shadowOpacity = 0.7
}
override init(frame: CGRect) {
super.init(frame: frame)
applyPlainShadow()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
applyPlainShadow()
}
}

Why is the background color of my drawable view black?

I would like the backgroundColor property of my UIView to be red or yellow. Though, the view displays a black color.
class RenderView:UIView {
var pointsToDraw:[Int] = [] {
didSet {
self.setNeedsDisplay()
}
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.backgroundColor = UIColor.whiteColor()
}
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, self.bounds);
CGContextSetLineWidth(context, 3);
UIColor.yellowColor().set()
if pointsToDraw.count > 4 {
CGContextMoveToPoint(context, CGFloat(pointsToDraw[0]), CGFloat(pointsToDraw[1]))
for i in 2..<pointsToDraw.count {
if i % 2 == 0 {
CGContextAddLineToPoint(context, CGFloat(pointsToDraw[i]), CGFloat(pointsToDraw[i + 1]))
}
}
}
// Draw
CGContextStrokePath(context);
}
}
The line I can draw on the view is yellow, but the view is still black. Here's a sample project. Why do you think this is happening?
It's because you're calling CGContextClearRect. The effect of this call can be quite tricky (as I explain in my online book). If you want the view's background to be red, then at the start of your drawRect: you should fill the context's bounds with red.

Add UIView to Storyboard without a XIB file

I am attempting to add a UIView without a nib file to the storyboard. I have dragged a UIView onto the storyboard and changed it's class to "Jahreskalender" with the Jahreskalender class as follows:
class Jahreskalender: UIView {
override func drawRect(rect: CGRect) {
// Drawing code
self.opaque = false
self.backgroundColor = UIColor(red: 100, green: 1, blue: 5, alpha: 0)
let jahresView = UIImageView(image: UIImage(named: "Calender.png"))
jahresView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
self.addSubview(jahresView)
}
}
When I run the app all I get is a black rectangle where I have dragged the UIView.
Do I need to load a XIB file or can I do everything with the "Jahreskalender" class programmaticall? Looked everywhere but can't find any tutorials.
Well, after some more trial and error I found my mistake. I need to call init(coder aDecoder: NSCoder).
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.opaque = false
}
Now it works. Thanks for the tips!

Gradient turns to black

I am using gradient in subview of UIView, after coming back to app by switching from some apps, the gradient becomes black. Don't know why, If anybody out there knows anything about this, help me out here.
Also i didn't subclass it programatically , directly put that in the UIView's class place in Storyboard itself (CustomGradientBlueView). Is that can be an issue.?
This is the code which am using for gradient ->
import UIKit
class CustomGradientBlueView: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
// colour declarations
let gradientColor1 = UIColor(red: 0.361, green: 0.145, blue: 0.553, alpha: 1.000)
let gradientColor2 = UIColor(red: 0.263, green: 0.537, blue: 0.635, alpha: 1.000)
// gradient decalarations
let gradient = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [gradientColor1.CGColor,gradientColor2.CGColor], [0,1])
//rectangle drawing
var screen = UIScreen.mainScreen().bounds.size
let rectanglePath = UIBezierPath(rect: CGRectMake(0,0, screen.width, screen.height))
CGContextSaveGState(context)
rectanglePath.addClip()
CGContextDrawLinearGradient(context, gradient, CGPointMake(0,0 ), CGPointMake(screen.width, screen.height), 0)
}
}
The problem is likely in these lines:
var screen = UIScreen.mainScreen().bounds.size
let rectanglePath = UIBezierPath(rect: CGRectMake(0,0, screen.width, screen.height))
CGContextSaveGState(context)
rectanglePath.addClip()
You are calling CGContextSaveGState without calling CGContextRestoreGState to balance it. That is very dangerous. If the context is maintained, which is perfectly possible, then you are saying addClip again with your previous clipping path already still in place - because you did not remove it with a balancing CGContextRestoreGState after drawing. Because of the nature of clipping paths, this causes you to end up with your whole view clipping, and your drawing is completely suppressed — hence the black view.
(It is not at all clear what you think you are doing with this clipping path in any case. There is no need to clip to the screen bounds, and accessing the screen bounds inside a view's drawRect: is a very odd thing to do. You might be happier just deleting those four lines altogether.)

Resources