CAGradientLayer on UILabel can't be rotated - ios

I want a horizontal color gradient as the text color of my UILabel.
So I'm using a CAGradientLayer as described in https://developer.apple.com/reference/quartzcore/cagradientlayer.
The gradient is rendered perfectly on the text, but vertically.
CATransform3DMakeRotation as apple described doesn't rotate the gradient.
In this answer it says that the CAGradientLayer needs to be added to a View or other Layer first to make the rotation work.
So I tried to add it as a sublayer to my UILabel and remove it after the rendering, but it won't transform the gradient on the text, but adds a horizontal gradient rectangle on top of it, in the size of the UILabel but rotated 90°.
Here's my code:
extension UILabel {
func addGradient() {
// Get size of the label
let size = CGSize(width: frame.width, height: frame.width)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(x: 0, y: 0, width: size.height, height: size.width)
gradientLayer.colors = [
UIColor.gradientBlue.cgColor,
UIColor.gradientPink.cgColor,
UIColor.gradientOrange.cgColor
]
// layer.addSublayer(gradientLayer)
gradientLayer.transform = CGAffineTransform
// This does not work
gradientLayer.transform = CATransform3DMakeRotation(CGFloat.pi / 2, 0, 0, 1)
UIGraphicsBeginImageContext(size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
// Create UIImage from Gradient
let gradientImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// gradientLayer.removeFromSuperlayer()
textColor = UIColor.init(patternImage: gradientImage!)
}
}

you can use
gradientLayer.startPoint = CGPoint.init(x: 0, y: 0)
gradientLayer.endPoint = CGPoint.init(x: 1, y: 1)
for diagonal gradient. you can play with those points however you want to achieve different results.

Related

How give gradient on text in TextView iOS?

I would like to give gradient effect on text in UITextView, when I try using gradient layer its applied on complete textview background. I just want it tp be applied on text and background coloraturas should separate.
I want output like this :
And its coming like this:
Can someone suggest how to achieve output like first image on UITextView
I am specifically looking solution for UITextView, instead UILabel or UIView
You can create a patterned color for your text. So, you can apply this color to any text component(like a label,textView, button, etc).
Please check the below example. where you can customize your color pattern in the getGradientLayer() method.
func gradientColor(bounds: CGRect, gradientLayer :CAGradientLayer) -> UIColor? {
//We are creating UIImage to get gradient color.
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIColor(patternImage: image!)
}
func getGradientLayer(bounds : CGRect) -> CAGradientLayer{
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
return gradient
}
let textView = UITextView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
textView.font = UIFont.boldSystemFont(ofSize:50)
textView.textAlignment = .center
textView.text = "Hello World!"
let gradient = getGradientLayer(bounds: textView.bounds)
textView.textColor = gradientColor(bounds: textView.bounds, gradientLayer: gradient)
Output:-
Easiest way to achieve this is to create a UIView with the desired gradient, over your UILabel or UITextView and then mask the gradient UIView over the textView.
There is a good tutorial here.

Why the edges of UIView are not smooth for huge corner radius?

class AttributedView: UIView {
private let gradient = CAGradientLayer()
var cornerRadius: CGFloat = 3 {
didSet {
layer.cornerRadius = cornerRadius
}
}
}
I simply use it for both: button view (corner radius: 20) and background circle (corner radius: 600).
Why button is smooth, and background is not?
With iOS 13.0 you can simple do, in addition to setting corner radius
yourView.layer.cornerCurve = .continuous
You should use bezzier paths and draw circle. After that you will receive nice, smooth edges.
let context = UIGraphicsGetCurrentContext()!
let gradient = CGGradient(colorsSpace: nil, colors: [UIColor.red.cgColor, UIColor.white.cgColor] as CFArray, locations: [0, 1])!
let ovalPath = UIBezierPath(ovalIn: CGRect(x: 64, y: 9, width: 111, height: 93))
context.saveGState()
ovalPath.addClip()
context.drawLinearGradient(gradient, start: CGPoint(x: 119.5, y: 9), end: CGPoint(x: 119.5, y: 102), options: [])
context.restoreGState()
UIBezierPath is a simple and efficient class for drawing shapes using Swift, which you can then put into CAShapeLayer, SKShapeNode, or other places. It comes with various shapes built in, so you can write code like this to create a rounded rectangle or a circle:
let rect = CGRect(x: 0, y: 0, width: 256, height: 256)
let roundedRect = UIBezierPath(roundedRect: rect, cornerRadius: 50)
let circle = UIBezierPath(ovalIn: rect)
You can also create custom shapes by moving a pen to a starting position then adding lines:
let freeform = UIBezierPath()
freeform.move(to: .zero)
freeform.addLine(to: CGPoint(x: 50, y: 50))
freeform.addLine(to: CGPoint(x: 50, y: 150))
freeform.addLine(to: CGPoint(x: 150, y: 50))
freeform.addLine(to: .zero)
If your end result needs a CGPath, you can get one by accessing the cgPath property of your UIBezierPath.
You probably should clip bounds of this view:
attributedView.clipsToBounds = true

Change navigationBar bottom border to a dashed line in swift

I would like to change my navigationBar to have a dotted line as its border like so:
I've found a wonderful answer in this thread about how to change the color of a navigationBar border: Change navigation bar bottom border color Swift In particular: https://stackoverflow.com/a/46224261/436014
Via a UIColor extension:
extension UIColor {
/// Converts this `UIColor` instance to a 1x1 `UIImage` instance and returns it.
///
/// - Returns: `self` as a 1x1 `UIImage`.
func as1ptImage() -> UIImage {
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
setFill()
UIGraphicsGetCurrentContext()?.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
UIGraphicsEndImageContext()
return image
}
}
I was wondering if there was any way to adapt this to build out a dashed line instead. I was thinking it could somehow be done by drawing a fill in alternating colors, but am confused since it's all an extension off of a single UIcolor
navigationController.navigationBar.shadowImage = UIColor.black.as1ptImage()
Thus, something like the following clearly won't work:
UIGraphicsBeginImageContext(CGSize(width: 1, height: 4))
UIGraphicsGetCurrentContext()?.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
UIGraphicsGetCurrentContext()?.fill(CGRect(x: 1, y: 0, width: 3, height: 1))
I was wondering if anyone has an idea how to go about this
Ok, I managed to find a solution through the help of the API documentation for CALayer: https://developer.apple.com/documentation/quartzcore/cashapelayer/1521921-linedashpattern
And this SO thread about converting a CALayer to UIImage: UIImage from CALayer in iOS
The overall process is:
Create a CALayer
Give this CALayer a frame
Draw a dashed line
Convert to UIImage
Use this UIImage as the navigationBar.shadowImage
The nice thing is that it tiles appropriately, so the CALayer/UIImage just need to be as wide as the dashed line you are generating.
extension UIImage {
class func imageWithLayer(layer: CALayer) -> UIImage {
UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.isOpaque, 0.0)
layer.render(in: UIGraphicsGetCurrentContext()!)
guard let img = UIGraphicsGetImageFromCurrentImageContext() else { return UIImage() }
UIGraphicsEndImageContext()
return img
}
}
let layer = CALayer()
layer.frame = CGRect(x: 0, y: 0, width: 6, height: 1)
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = 1
shapeLayer.lineDashPattern = [4,2]
let path = CGMutablePath()
path.addLines(between: [CGPoint(x:1, y: 0), CGPoint(x: 6, y:0)])
shapeLayer.path = path
layer.addSublayer(shapeLayer)
navigationController.navigationBar.shadowImage = UIImage.imageWithLayer(layer: layer)

Views drawn from code (PaintCode) are pixelated, very pixelated when scaled

I am building an app that overlays views drawn with code (output from PaintCode) onto photos. I have added gesture recognizers to rotate and scale the views drawn with code.
There is some mild pixelation on the views drawn on top. If I do any rotation or scale the image larger (even a slight bit), there is a lot more pixelation.
Here is a comparison of the images:
No rotating or scaling:
A small amount of rotation/scaling:
Here is the UIView extension I'm using to output the composited view:
extension UIView {
func printViewToImage() -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = 2.0
let renderer = UIGraphicsImageRenderer(bounds: self.bounds, format: format)
return renderer.image { rendererContext in
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
}
}
}
Even if I set the scale to something like 4.0, there is no difference.
Here is the code I'm using for the scale/rotation gesture recognizers:
#IBAction func handlePinch(recognizer: UIPinchGestureRecognizer) {
guard let view = recognizer.view else {
return
}
view.transform = view.transform.scaledBy(x: recognizer.scale, y: recognizer.scale)
recognizer.scale = 1
}
#IBAction func handleRotate(recognizer: UIRotationGestureRecognizer) {
guard let view = recognizer.view else {
return
}
view.transform = view.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0
}
I have experimented with making the canvasses very large in PaintCode (3000x3000), and there is no difference, so I don't think it has to do with that.
How can I draw/export these views so that they are not pixelated?
Edit: Here's what some of the drawing code looks like...
public dynamic class func drawCelebrateDiversity(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 3000, height: 3000), resizing: ResizingBehavior = .aspectFit, color: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Resize to Target Frame
context.saveGState()
let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 3000, height: 3000), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 3000, y: resizedFrame.height / 3000)
//// Bezier 13 Drawing
let bezier13Path = UIBezierPath()
bezier13Path.move(to: CGPoint(x: 2915.18, y: 2146.51))
bezier13Path.addCurve(to: CGPoint(x: 2925.95, y: 2152.38), controlPoint1: CGPoint(x: 2919.93, y: 2147.45), controlPoint2: CGPoint(x: 2924.05, y: 2147.91))
When scaling UIViews (or custom CALayers), you should set their contentsScale to match the desired density of their content. UIViews set their layer contentsScale to screen scale (2 on retina), and you need to multiply this with the extra scale you do via transform.
view.layer.contentsScale = UIScreen.main.scale * gesture.scale;
Even if the drawing code is resolution independent, everything on screen must be converted to bitmap at some time. UIView allocates bitmap with size of bounds.size * contentsScale and then invokes -drawRect:/draw(_ rect:) method.
It is important to set contentsScale on that view that draws, even if that view is not scaled (but some of its parent is). A common solution is to recursively set contentsScale on all sublayers of the scaled view.
– PaintCode Support

Cut transparent hole in the shape of UIImage into UIView

I have an UIView in which I want to create a hole in the shape of the content of a UIImageView, but I can't get it to work. I can, however, mask an UIView to the shape of the UIImage.
My code:
let doorsMaskBounds = CGRect(x: 0, y: 0, width: 111, height: 111)
// Background view
let doorsBgView = UIView(frame: doorsMaskBounds)
doorsBgView.center = navigationController.view.center
doorsBgView.backgroundColor = UIColor.yellow
vc.view.addSubview(doorsBgView)
vc.view.bringSubview(toFront: doorsBgView)
// Logo Mask
doorsBgView.layer.mask = CALayer()
doorsBgView.layer.mask?.contents = UIImage(named: "doors")!.cgImage
doorsBgView.layer.mask?.bounds = doorsMaskBounds
doorsBgView.layer.mask?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
doorsBgView.layer.mask?.position = CGPoint(x: doorsBgView.frame.width / 2, y: doorsBgView.frame.height / 2)
which results into:
I know how to "cut" a hole into a UIView in form of a shape I draw myself, as explained here, I just can't seem to figure out how the get the shape of the UIImage and apply it as the cut.

Resources