Wrong size of gradient border on image after fast scroll on tvOS - swift

I have an app on tvOS and collection view with horizontal scroll. When the item is focused the ImageView grows a bit. I also added gradient border to focused ImageView inside this cellView. I'm adding gradient border based on this answer https://stackoverflow.com/a/62091073/4037291
public extension UIView {
private static let kLayerNameGradientBorder = "GradientBorderLayer"
func gradientBorder(width: CGFloat,
colors: [UIColor],
startPoint: CGPoint = CGPoint(x: 0.5, y: 0.0),
endPoint: CGPoint = CGPoint(x: 0.5, y: 1.0),
andRoundCornersWithRadius cornerRadius: CGFloat = 0) {
let existingBorder = gradientBorderLayer()
let border = existingBorder ?? CAGradientLayer()
border.frame = CGRect(x: bounds.origin.x, y: bounds.origin.y,
width: bounds.size.width + width, height: bounds.size.height + width)
border.colors = colors.map { return $0.cgColor }
border.startPoint = startPoint
border.endPoint = endPoint
let mask = CAShapeLayer()
let maskRect = CGRect(x: bounds.origin.x + width/2, y: bounds.origin.y + width/2,
width: bounds.size.width - width, height: bounds.size.height - width)
mask.path = UIBezierPath(roundedRect: maskRect, cornerRadius: cornerRadius).cgPath
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.white.cgColor
mask.lineWidth = width
border.mask = mask
let exists = (existingBorder != nil)
if !exists {
layer.addSublayer(border)
}
}
private func gradientBorderLayer() -> CAGradientLayer? {
let borderLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameGradientBorder }
if borderLayers?.count ?? 0 > 1 {
fatalError()
}
return borderLayers?.first as? CAGradientLayer
}
}
In general it works fine but sometimes when I scroll fast the border doesn't reflect size of the view.
The image bellow shows how the border looks when I scroll fast.
I'm adding gradient border to and image like this.
view.logoView.addGradientBorder(
width: 4,
colors: AppTheme.Color.gradient,
startPoint: .topLeft,
endPoint: .bottomRight,
andRoundCornersWithRadius: 4
)
The borders should be around image even after fast scroll.
Thank you for your help.

Related

Programmatically drawing custom oval shape on screen swift

How can i draw custom oval shape like in below image in swift (not swiftUI).
Thank you in advance
I have tried to clone similar view using UIView and was able to create similar UI as you have stated on screenshot
And here is my code snippet
let width: CGFloat = view.frame.size.width
let height: CGFloat = view.frame.size.height
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height), cornerRadius: 0)
let rect = CGRect(x: width / 2 - 150, y: height / 2.5 - 100, width: 300, height: 400)
let circlePath = UIBezierPath(ovalIn: rect)
path.append(circlePath)
path.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.path = path.cgPath
fillLayer.fillRule = .evenOdd
fillLayer.fillColor = UIColor.red.cgColor
fillLayer.opacity = 0.5
view.layer.addSublayer(fillLayer)
Hope this helps you. Good day.
You can draw oval path like this
class CustomOval: UView {
override func drawRect(rect: CGRect) {
var ovalPath = UIBezierPath(ovalIn: rect)
UIColor.gray.setFill()
ovalPath.fill()
}
}

Swift make UIView border into Gradient Color

I want to create a gradient border on a UIView.
The following code can produce a solid for the view Box1.
nothing I have found allows me to change the frameColor to a gradient.
var frameColor = UIColor.red.cgColor
var leftBorder = CALayer()
leftBorder.frame = CGRect(x: 0.0, y: 0, width: 1, height: box1.frame.size.height )
leftBorder.backgroundColor = frameColor
I am using Swift 5 and Xcode 11.
Thank you.
Try with below extension
public extension UIView {
private static let kLayerNameGradientBorder = "GradientBorderLayer"
func setGradientBorder(
width: CGFloat,
colors: [UIColor],
startPoint: CGPoint = CGPoint(x: 0.5, y: 0),
endPoint: CGPoint = CGPoint(x: 0.5, y: 1)
) {
let existedBorder = gradientBorderLayer()
let border = existedBorder ?? CAGradientLayer()
border.frame = bounds
border.colors = colors.map { return $0.cgColor }
border.startPoint = startPoint
border.endPoint = endPoint
let mask = CAShapeLayer()
mask.path = UIBezierPath(roundedRect: bounds, cornerRadius: 0).cgPath
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.white.cgColor
mask.lineWidth = width
border.mask = mask
let exists = existedBorder != nil
if !exists {
layer.addSublayer(border)
}
}
func removeGradientBorder() {
self.gradientBorderLayer()?.removeFromSuperlayer()
}
private func gradientBorderLayer() -> CAGradientLayer? {
let borderLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameGradientBorder }
if borderLayers?.count ?? 0 > 1 {
fatalError()
}
return borderLayers?.first as? CAGradientLayer
}
}
Usage
self.borderView.setGradientBorder(width: 5.0, colors: [UIColor.red , UIColor.blue])
I hope this will reach your requirement.
https://medium.com/#kushal0409/gradient-button-3dc8ef763039
There is no off-the-shelf way to do this, but you could build it yourself.
Off the top of my head, the approach might look like this:
Create a CAGradientLayer and set it up to draw your gradient as
desired.
Create a CAShapeLayer that contains an empty rectangle that is your
frame.
Add your shape layer as the mask of your gradient layer.
Add the gradient layer to your view as a sub-layer of the view's
content layer.
Swift 5 extension (works with corner radius)
extension UIView {
func gradientBorder(colors: [UIColor], isVertical: Bool){
self.layer.masksToBounds = true
//Create gradient layer
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPoint.zero, size: self.bounds.size)
gradient.colors = colors.map({ (color) -> CGColor in
color.cgColor
})
//Set gradient direction
if(isVertical){
gradient.startPoint = CGPoint(x: 0.5, y: 0)
gradient.endPoint = CGPoint(x: 0.5, y: 1)
}
else {
gradient.startPoint = CGPoint(x: 0, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 0.5)
}
//Create shape layer
let shape = CAShapeLayer()
shape.lineWidth = 1
shape.path = UIBezierPath(roundedRect: gradient.frame.insetBy(dx: 1, dy: 1), cornerRadius: self.layer.cornerRadius).cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradient.mask = shape
//Add layer to view
self.layer.addSublayer(gradient)
gradient.zPosition = 0
}
}

Rounded Curve Edge for UIView

I am trying to add rounded edges via this extension , however I am getting proper rounded UIView in iPhone X but when I try to run this same code in iPhone 6s , its like not getting curved , tried to increase the desiredCurve still not able to see the curve.
extension UIView {
/* Usage Example
* bgView.addBottomRoundedEdge(desiredCurve: 1.5)
*/
func addBottomRoundedEdge(desiredCurve: CGFloat?) {
let offset: CGFloat = self.frame.width / desiredCurve!
let bounds: CGRect = self.bounds
let rectBounds: CGRect = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.size.width, height: bounds.size.height / 2)
let rectPath: UIBezierPath = UIBezierPath(rect: rectBounds)
let ovalBounds: CGRect = CGRect(x: bounds.origin.x - offset / 2, y: bounds.origin.y, width: bounds.size.width + offset, height: bounds.size.height)
let ovalPath: UIBezierPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
// Create the shape layer and set its path
let maskLayer: CAShapeLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
// Set the newly created shape layer as the mask for the view's layer
self.layer.mask = maskLayer
}
}
Call the addBottomRoundedEdge extension method to add the bottom curve on a UIView
extension UIView {
func dropShadow(scale: Bool = true) {
layer.masksToBounds = true
layer.shadowColor = UIColor.lightGray.cgColor
layer.shadowOpacity = 0.8
layer.shadowOffset = CGSize(width: 0, height: 5)
layer.shadowRadius = 2
layer.borderWidth = 1
layer.shouldRasterize = true
layer.rasterizationScale = scale ? UIScreen.main.scale : 1
layer.borderColor = UIColor(red:222/255, green:225/255, blue:227/255, alpha: 1).cgColor
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: 5).cgPath
}
func addBottomRoundedEdge() {
let offset: CGFloat = (self.frame.width * 1.5)
let bounds: CGRect = self.bounds
let rectBounds: CGRect = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.size.width , height: bounds.size.height / 2)
let rectPath: UIBezierPath = UIBezierPath(rect: rectBounds)
let ovalBounds: CGRect = CGRect(x: bounds.origin.x - offset / 2, y: bounds.origin.y, width: bounds.size.width + offset , height: bounds.size.height)
let ovalPath: UIBezierPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
let maskLayer: CAShapeLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
self.layer.mask = maskLayer
}
}
You can use Layer.corner.Radius on the orange view.
First put that view width bigger than the super view:
#IBOutlet weak var orangeView: UIView!
self.orangeView.frame. width = self.view.frame + 50
self.orangeView.layer.corner.Radius = self.orangeView.frame.height /2

swift drawing borders around textview or labels

myLabel.layer.borderWidth = 0.5
myLabel.layer.borderColor = UIColor.green.cgColor
Thats a simple answer of drawing a border. However the issue is I need to draw the border only to the right and bottom. I also need another label to draw border on top and right. How can I draw border to specific sides, not all 4 sides?
Add a shape layer and draw the lines in that layer. Note that layers do not participate in auto layout so you need to put your code in viewDidLayoutSubviews or subclass uilabel and do this in layout subviews. Here is a playground example using the UILabel subclass:
import PlaygroundSupport
import UIKit
class L: UILabel {
var strokeColor = UIColor.blue
var strokeWidth = CGFloat(0.5)
private lazy var labelBorderLayer:CAShapeLayer = {
let shapeLayer = CAShapeLayer()
self.layer.addSublayer(shapeLayer)
return shapeLayer
}()
override func layoutSubviews() {
super.layoutSubviews()
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: bounds.size.height))
path.addLine(to: CGPoint(x: bounds.size.width, y: bounds.size.height))
path.addLine(to: CGPoint(x: bounds.size.width, y: 0))
labelBorderLayer.path = path
labelBorderLayer.strokeColor = strokeColor.cgColor
labelBorderLayer.lineWidth = strokeWidth
labelBorderLayer.fillColor = UIColor.clear.cgColor
}
}
let v = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
let l = L(frame: v.frame.insetBy(dx: 50, dy: 80))
v.addSubview(l)
l.textColor = .white
l.textAlignment = .center
l.text = "gjgkjgjgjgj"
v.backgroundColor = .red
PlaygroundPage.current.liveView = v
Im not sure if you can do that with the actual label border. But but you can do something similar to this
public enum UIButtonBorderSide {
case Top, Bottom, Left, Right
}
extension UIButton {
public func addBorder(side: UIButtonBorderSide, color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.CGColor
switch side {
case .Top:
border.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: width)
case .Bottom:
border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height: width)
case .Left:
border.frame = CGRect(x: 0, y: 0, width: width, height: self.frame.size.height)
case .Right:
border.frame = CGRect(x: self.frame.size.width - width, y: 0, width: width, height: self.frame.size.height)
}
self.layer.addSublayer(border)
}
}
Thats for UIButton, But you can probably adapt it easily.
Source: https://gist.github.com/Isuru-Nanayakkara/496d5713e61125bddcf5
Hope this helps

Shadow left and right of UIView

Hi i like to add an CALayer Shadow to an View where the shadow was left and right of the view the simplest way was:
someView.layer.shadowColor = [[UIColor blackColor] CGColor];
someView.layer.shadowOffset = CGSizeMake(0.0f,0.0f);
someView.layer.shadowOpacity = 1.0f;
someView.layer.shadowRadius = 10.0f;
someView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:someView.bounds] CGPath];
but i will add a bigger shadow as like a shadow when i increase the shadowRadius it do not look good. How i can make a shadow that looks good at left and right.
I think 10 is a pretty big shadow radius, try 3 or 4 instead, also opacity I usually use 0.7:
someView.layer.shadowColor = [[UIColor blackColor] CGColor];
someView.layer.shadowOffset = CGSizeMake(0.0f,0.0f);
someView.layer.shadowOpacity = 0.7f;
someView.layer.shadowRadius = 4.0f;
If you want the shadow only on left and right, then inset the rectangle on the top and bottom so the top and bottom shadow are hidden behind your view:
CGRect shadowRect = CGRectInset(someView.bounds, 0, 4); // inset top/bottom
someView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:shadowRect] CGPath];
I'm not really sure if that's what you wanted.
swift 3.0 version
imageView.layer.shadowOpacity = 0.8
imageView.layer.shadowOffset = CGSize(width: 0, height: 3)
imageView.layer.shadowRadius = 4.0
let shadowRect: CGRect = imageView.bounds.insetBy(dx: 0, dy: 4)
imageView.layer.shadowPath = UIBezierPath(rect: shadowRect).cgPath
progrmr's answer was very helpful, cheers!
I made a slide-out menu and I had a problem with the shadow surrounding my VC and disrupting the navigation bar. Turned out I had to inset the shadow layer.
Here's my solution using swift:
rightViewController!.view.layer.shadowOpacity = 0.8
rightViewController!.view.layer.shadowOffset = CGSizeMake(0, 3)
rightViewController!.view.layer.shadowRadius = 4.0
let shadowRect: CGRect = CGRectInset(rightViewController!.view.bounds, 0, 4); // inset top/bottom
rightViewController!.view.layer.shadowPath = UIBezierPath(rect: shadowRect).CGPath
I created a shadow that can be used at the top/bottom or left/right, without prejudice at all. No shadow will appear in the left/right if you choose top/bottom, and so on. I hope I helped. Here's the code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let imgView = UIImageView(frame: CGRect(x: self.view.bounds.midX - 150, y: self.view.bounds.midY - 150, width: 300, height: 300))
imgView.contentMode = .scaleToFill
imgView.image = UIImage(named: "name image")
self.view.addSubview(imgView)
//You can put left/right or top/bottom
imgView.addShadow(sides: .leftRight, constant: 10, alpha: 5)
}
}
extension UIView{
func addShadow(sides: Sides, constant: Int, alpha: Int){
if(constant > 0 && alpha > 0){
switch sides {
case .leftRight:
let view = UIView(frame: CGRect(x: -CGFloat(constant), y: 0, width: self.bounds.width + CGFloat((constant * 2)), height: self.bounds.height))
self.addSubview(view)
addMask(sides: .leftRight, view: view, constant: constant, shadowRadius: alpha)
case .topBottom:
let view = UIView(frame: CGRect(x: 0, y: -CGFloat(constant), width: self.bounds.width , height: self.bounds.height + CGFloat(constant * 2)))
self.addSubview(view)
addMask(sides: .topBottom, view: view, constant: constant, shadowRadius: alpha)
}
}
}
private func addMask(sides:Sides, view: UIView, constant: Int, shadowRadius:Int){
let mutablePath = CGMutablePath()
let mask = CAShapeLayer()
switch sides {
case .leftRight:
mutablePath.addRect(CGRect(x: -CGFloat(constant), y: 0, width: view.bounds.width, height: view.bounds.height))
mutablePath.addRect(CGRect(x: CGFloat(constant) , y: 0, width: view.bounds.width , height: view.bounds.height))
case .topBottom:
mutablePath.addRect(CGRect(x: 0, y: -CGFloat(constant), width: view.bounds.width, height: view.bounds.height))
mutablePath.addRect(CGRect(x: 0, y: CGFloat(constant) , width: view.bounds.width , height: view.bounds.height))
}
mask.path = mutablePath
mask.fillRule = CAShapeLayerFillRule.evenOdd
mask.fillColor = UIColor(white:1.0, alpha: 0.2).cgColor
view.layer.mask = mask
view.layer.addSublayer(mask)
mask.shadowColor = UIColor.black.cgColor
mask.shadowRadius = CGFloat(shadowRadius)
mask.shadowOpacity = 1.0
mask.shadowOffset = CGSize(width: 0, height: 0)
}
}
enum Sides{
case leftRight
case topBottom
}
You just need to call the extension UIView, which has the addShadow() function and pass the parameters you want.