Apply Beveled Radial Gradient to UIView - ios

I'm currently using the following code here
#IBDesignable class RadialGradientView: UIView {
#IBInspectable var outsideColor: UIColor = UIColor.red
#IBInspectable var insideColor: UIColor = UIColor.green
override func draw(_ rect: CGRect) {
let colors = [insideColor.cgColor, outsideColor.cgColor] as CFArray
let endRadius = sqrt(pow(frame.width/2, 2) + pow(frame.height/2, 2))
let center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
let context = UIGraphicsGetCurrentContext()
context?.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: endRadius, options: CGGradientDrawingOptions.drawsBeforeStartLocation)
}
}
This code will draw a radial gradient that looks like this:
What I am trying to do is make it look like below (which is how it looks in the current desktop version of the app).
You will notice that in the desktop version the gradient has blue at the top and bottom and the white area is stretched to include more of the text area so it's easier to read.
How can I recreate this effect? I have tried for hours playing with different numbers and settings in the current code to no avail so far (such as changing the endRadius).
Any help or ideas are appreciated.

Related

Gradient Transparent view swift

im trying to make a gradient transparent view alert as shown in the picture below, any help ?
#IBInspectable var InsideColor: UIColor = UIColor.clear
#IBInspectable var OutsideColor: UIColor = UIColor.clear
override func draw(_ rect: CGRect) {
let colors = [InsideColor.cgColor, OutsideColor.cgColor] as CFArray
let endRadius = min(frame.width, frame.height) / 2
let center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
UIGraphicsGetCurrentContext()!.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: endRadius, options: CGGradientDrawingOptions.drawsBeforeStartLocation)
}
It is better to have it statically in your project assets instead of creating it dynamically every time. So:
Create an image with the clear center and black fill color like this:
Note that the center part is NOT white. It's transparent. So you should see behind it like this example:
Then add an imageView and fill it with this image.
Set the backgroundColor of the image view to .clear
done.
Note: Don't forget to set the backgroundColor of the original view of your custom alert to .clear and modalPresentationStyle of the custom alert controller to .overCurrentContext

Radial gradient uses unexpected gray color

TL;DR
Does anyone know how I can smooth out the gradient transition from red to white in a radial gradient? Because I'm getting an ugly gray color in my gradient.
Details:
I created a custom UIView with the following in draw rect
override func draw(_ rect: CGRect) {
let colors = [UIColor.red.cgColor, UIColor.clear.cgColor] as CFArray
let divisor: CGFloat = 3
let endRadius = sqrt(pow(frame.width/divisor, 2) + pow(frame.height/divisor, 2))
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
let context = UIGraphicsGetCurrentContext()
context?.saveGState()
context?.drawRadialGradient(gradient!,
startCenter: center,
startRadius: 0.0,
endCenter: center,
endRadius: endRadius,
options: .drawsBeforeStartLocation)
context?.restoreGState()
}
I set the custom view's color to red and added the view to the viewcontroller's view, which has a white background.
I wanted the gradient to go from red to white but it seems to go from red to gray. I tried changing the second color in the gradient array to white, and though it did look slightly better, the grayness still remains.
When using clear color on transparent gradients keep in mind that the clear color is generated by applying the black color an alpha and thats why you get the greyish color on your case.
To solve this in your code change UIColor.clear.cgColor, from colors array, with UIColor.white.withAlphaComponent(0.0).cgColor. This way you can get the gradient you expect.

Why does CGBlendMode.color work correctly in simulator but fail on device?

I'm using CAShapeLayer's render(in:) with blend mode set to CGBlendMode.color to tint an image drawn to the current CGContext. It works perfectly in the simulator, but not on the device.
Screenshot on the left is a simulated iPhone 7 Plus, on the right is the actual device running iOS 10.2. First the black square is drawn, then the red circle, then the CGBlendMode is set to color and the blue circle is drawn.
The documentation for CGBlendMode.color says:
Uses the luminance values of the background with the hue and
saturation values of the source image.
So where the blue circle overlaps the black background, it should use luminance of zero and draw black.
Why does this work correctly in the simulator but not on the real device? And more importantly, how can I get this type of blend to work on the device?
To reproduce, create a new project in Xcode 8.2.1 with the "Single View Application" template and replace ViewController.swift with:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let blendModeTestView = BlendModeTest(frame: CGRect(x: 100, y: 100, width: 250, height: 250))
view.addSubview(blendModeTestView)
}
}
class BlendModeTest: UIView {
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
// black background
let backgroundPath = UIBezierPath(rect: rect)
let backgroundShapeLayer = CAShapeLayer()
backgroundShapeLayer.path = backgroundPath.cgPath
backgroundShapeLayer.fillColor = UIColor.black.cgColor
backgroundShapeLayer.render(in: context)
// red circle
let circlePath = UIBezierPath(arcCenter: CGPoint(x: rect.size.width/2, y: rect.size.height/2),
radius: rect.size.width/4,
startAngle: CGFloat(0),
endAngle:CGFloat(M_PI * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.fillColor = UIColor.red.cgColor
shapeLayer.render(in: context)
// blue circle (with .color blend mode)
let colorFillPath = UIBezierPath(arcCenter: CGPoint.zero,
radius: rect.size.width * 0.8,
startAngle: CGFloat(0),
endAngle:CGFloat(M_PI * 2),
clockwise: true)
let colorFillShapeLayer = CAShapeLayer()
colorFillShapeLayer.path = colorFillPath.cgPath
colorFillShapeLayer.fillColor = UIColor.blue.cgColor
context.setBlendMode(.color)
colorFillShapeLayer.render(in: context)
}
}

Bad quality for my Radial Gradient

I am drawing a radial gradient in UIView's drawRect function which is pretty simple, the problem I have is that on device (iPhone 6S plus) gradient quality is pretty bad.
Do you have an idea why?
EDIT: I noticed that gradient is bad if my view or color has an alpha value less than 1.0.
private func updateGradient() {
let colorSpace = CGColorSpaceCreateDeviceRGB()
let colors:[CGColor] = [haloColor.CGColor, UIColor.clearColor().CGColor]
gradient = CGGradientCreateWithColors(colorSpace, colors, [0.0,0.8])
setNeedsDisplay()
}
override public func drawRect(rect: CGRect) {
if let gradient = self.gradient {
let context = UIGraphicsGetCurrentContext()
let options: CGGradientDrawingOptions = [.DrawsAfterEndLocation]
let center = CGPoint(x: bounds.midX, y: -70)
CGContextDrawRadialGradient(context, gradient, center, 0, center, size.height*3, options)
}
}

Swift - how to create a non-pixelated radial gradient?

I'm trying to create a radial / circular gradient for my app.
I used a solution similar to the confirmed answer here
However, the resulting gradient looks pixelated.
What am I doing wrong? Is there are more modern way or a good 3rd party framework to create smooth, simple radial gradients?
Thanks!
My code (based on snippets published on Stack Overflow):
class RadialGradientLayer: CALayer {
init(innerColor: UIColor, outerColor: UIColor) {
self.innerColor = innerColor.CGColor
self.outerColor = outerColor.CGColor
super.init()
needsDisplayOnBoundsChange = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
var innerColor: CGColor
var outerColor: CGColor
override func drawInContext(ctx: CGContext) {
CGContextSaveGState(ctx)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let locations:[CGFloat] = [0.0, 1.0]
let colors = [innerColor, outerColor]
let gradient = CGGradientCreateWithColors(colorSpace, colors, locations)
let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
let radius = min(bounds.width / 2.0, bounds.height / 2.0)
CGContextDrawRadialGradient(ctx, gradient, center, 0.0, center, radius, CGGradientDrawingOptions(rawValue: 0))
}
}
You need to make sure the CGContext is scaled for the devices screen.
Take a look at the answers to this question - CGContext and retina display
This will help you fix the issue.

Resources