I have a chart like below:
I have a problem with animation and shadows.
i draw a gradient with animation but from the beginning i have the shadow and mask layer which i don't want, i want the shadow animating with the gradient.
the current chart with animation is like below.
i dont want the user see the shadow and mask layer from the beginning.
here is my code:
import Foundation
import UIKit
#IBDesignable class CircularProgressView: UIView {
#IBInspectable var containerCircleColor: UIColor = UIColor.lightGray
#IBInspectable var gradientStartColor: UIColor = UIColor.green
#IBInspectable var gradientEndColor: UIColor = UIColor.yellow
#IBInspectable var arcWidth: CGFloat = 20
override init(frame: CGRect) {
super.init(frame: frame)
circularProgressView_init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
circularProgressView_init()
}
fileprivate func circularProgressView_init() {
let viewHeight = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
self.addConstraint(viewHeight)
}
override func prepareForInterfaceBuilder() {
circularProgressView_init()
}
override func draw(_ rect: CGRect) {
let width = self.bounds.width
let center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
let radius: CGFloat = (width - (arcWidth * 2.5)) / 2
let progressStartAngle: CGFloat = 3 * CGFloat.pi / 2
let progressEndAngle: CGFloat = CGFloat.pi / 2
//fill circular
let circlePath = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: 0,
endAngle: 360,
clockwise: true)
circlePath.lineWidth = arcWidth
containerCircleColor.setStroke()
circlePath.stroke()
//MARK: ProgressPath
let progressPath = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: progressStartAngle,
endAngle: progressEndAngle,
clockwise: true)
progressPath.lineWidth = arcWidth
progressPath.lineCapStyle = .round
//MARK: Gradient
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [gradientStartColor.cgColor , gradientEndColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x:1, y:1)
gradientLayer.frame = self.bounds
//MARK: Animation
let anim = CABasicAnimation(keyPath: "strokeEnd")
anim.duration = 2
anim.fillMode = kCAFillModeForwards
anim.fromValue = 0
anim.toValue = 1
//MARK: Mask Layer
let maskLayer = CAShapeLayer()
maskLayer.path = progressPath.cgPath
maskLayer.fillColor = UIColor.clear.cgColor
maskLayer.strokeColor = UIColor.black.cgColor
maskLayer.lineWidth = arcWidth
maskLayer.lineCap = kCALineCapRound
gradientLayer.mask = maskLayer
self.layer.insertSublayer(gradientLayer, at: 0)
let context = UIGraphicsGetCurrentContext()
let shadow = UIColor.lightGray
let shadowOffset = CGSize(width: 3.1, height: 3.1)
let shadowBlurRadius: CGFloat = 5
context!.saveGState()
context!.setShadow(offset: shadowOffset, blur: shadowBlurRadius, color: (shadow as UIColor).cgColor)
progressPath.stroke()
context?.restoreGState()
maskLayer.add(anim, forKey: nil)
gradientLayer.add(anim, forKey: nil)
}
}
Is it possible at all?
If it is not, how can i at least hide the shadow and mask and show it after animation ends?
You should create another layer to give the shadow.
1 - first you have to create a UIView as a shadowLayer
2 - then mask this shadowLayer with the path and layer same as the maskLayer(in your code) you have already created. e.g: shadowMaskLayer
3 - add the shadow properties to this new shadowMaskLayer
4 - then add the shadowLayer to the original UIView CircularProgressView
5 - also add the animation you already have to this shadowLayer to animate the shadow with the whole circle.
Don't hesitate to ask questions ;)
Well, i put the correct answer here, maybe some one need it in the future.
import Foundation
import UIKit
#IBDesignable class CircularProgressView: UIView {
#IBInspectable var containerCircleColor: UIColor = UIColor.lightGray
#IBInspectable var gradientStartColor: UIColor = UIColor.yellow
#IBInspectable var gradientEndColor: UIColor = UIColor.red
#IBInspectable var arcWidth: CGFloat = 20
#IBInspectable var progressPercent: CGFloat = 50
override init(frame: CGRect) {
super.init(frame: frame)
circularProgressView_init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
circularProgressView_init()
}
fileprivate func circularProgressView_init() {
let viewHeight = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
self.addConstraint(viewHeight)
}
override func prepareForInterfaceBuilder() {
circularProgressView_init()
}
override func draw(_ rect: CGRect) {
let width = self.bounds.width
let center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
let radius: CGFloat = (width - (arcWidth * 2.5)) / 2
let progressStartAngle: CGFloat = 3 * CGFloat.pi / 2
let progressEndAngle: CGFloat = ConvertToTrigonometry.shared.trigonimetryCordinate(percentage: progressPercent) //CGFloat.pi / 2
//fill circular
let circlePath = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: 0,
endAngle: 360,
clockwise: true)
circlePath.lineWidth = arcWidth
containerCircleColor.setStroke()
circlePath.stroke()
//MARK: ProgressPath
let progressPath = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: progressStartAngle,
endAngle: progressEndAngle,
clockwise: true)
progressPath.lineWidth = arcWidth
progressPath.lineCapStyle = .round
//MARK: Gradient
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [gradientStartColor.cgColor , gradientEndColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x:1, y:1)
gradientLayer.frame = self.bounds
//MARK: Mask Layer
let maskLayer = CAShapeLayer()
maskLayer.path = progressPath.cgPath
maskLayer.fillColor = UIColor.clear.cgColor
maskLayer.backgroundColor = UIColor.black.cgColor
maskLayer.strokeColor = UIColor.black.cgColor
maskLayer.lineWidth = arcWidth
maskLayer.lineCap = kCALineCapRound
maskLayer.masksToBounds = false
gradientLayer.mask = maskLayer
//MARK: Shadow
let shadowLayer = CAShapeLayer()
shadowLayer.frame = bounds
shadowLayer.backgroundColor = UIColor.gray.cgColor
layer.addSublayer(shadowLayer)
let maskShadowLayer = CAShapeLayer()
maskShadowLayer.path = progressPath.cgPath
maskShadowLayer.fillColor = UIColor.clear.cgColor
maskShadowLayer.backgroundColor = UIColor.black.cgColor
maskShadowLayer.strokeColor = UIColor.black.cgColor
maskShadowLayer.lineWidth = arcWidth
maskShadowLayer.lineCap = kCALineCapRound
maskShadowLayer.masksToBounds = false
maskShadowLayer.shadowColor = UIColor.black.cgColor
maskShadowLayer.shadowOpacity = 0.5
maskShadowLayer.shadowOffset = CGSize(width: 3.1, height: 3.1)
shadowLayer.mask = maskShadowLayer
//MARK: Animation
let anim = CABasicAnimation(keyPath: "strokeEnd")
anim.duration = 2
anim.fillMode = kCAFillModeForwards
anim.fromValue = 0
anim.toValue = 1
maskShadowLayer.add(anim, forKey: nil)
maskLayer.add(anim, forKey: nil)
gradientLayer.add(anim, forKey: nil)
layer.addSublayer(gradientLayer)
}
}
And also a helper class which i use for trigonometry conversion:
import Foundation
import UIKit
class ConvertToTrigonometry {
static let shared = ConvertToTrigonometry()
func trigonimetryCordinate(percentage: CGFloat) -> CGFloat {
let pi = CGFloat.pi
let trigonometryRatio = (percentage * 360) / 100 // How much you want to move forward in axis.
let endPointDegree = (3 * pi / 2) + ((trigonometryRatio * 2 / 360) * pi) // End point on axis based on your trigonometryRatio and the start point which is 3pi/2
return endPointDegree
}
}
this solution draw an arc with animating gradient and shadow.
you can find the complete project in my Github.
Related
can someone tell me please how to make the button rounded, shadow and gradient
here I set the gradient and shadow to the button, but without rounding:
#IBOutlet weak var info: UIButton!
info.setTitle("INFO", for: .normal)
info.setTwoGradients(colorOne: Colors.OrangeGrad, colorTwo: Colors.OrangeGradSec)
info.setTitleColor(UIColor.black, for: .normal)
info.layer.shadowColor = UIColor.darkGray.cgColor
info.layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
info.layer.shadowOpacity = 0.8
info.layer.shadowRadius = 2
view.addSubview(info)
I know that the shadow disappears because of the rounding and I found a way to fix it, ex:
final class CustomButton: UIButton {
private var shadowLayer: CAShapeLayer!
override func layoutSubviews() {
super.layoutSubviews()
if shadowLayer == nil {
shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 5.0, height: 2.0)
shadowLayer.shadowOpacity = 0.7
shadowLayer.shadowRadius = 2
layer.insertSublayer(shadowLayer, at: 0)
}
}
but there is a line:
shadowLayer.fillColor = UIColor.white.cgColor
because of which I can't set the gradient
therefore, I cannot find a way by which all three conditions would be met
Output:
Usage
class ViewController: UIViewController {
#IBOutlet var btnGradient: CustomButton!
override func viewDidLoad() {
super.viewDidLoad()
btnGradient.gradientColors = [.red, .green]
btnGradient.setTitle("Gradient Button", for: .normal)
btnGradient.setTitleColor(.white, for: .normal)
}
}
Custom Class
Use this class as a reference to setup the attributes:
class CustomButton: UIButton {
var gradientColors : [UIColor] = [] {
didSet {
setupView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
let startPoint = CGPoint(x: 0, y: 0.5)
let endPoint = CGPoint(x: 1, y: 0.5)
var btnConfig = UIButton.Configuration.plain()
btnConfig.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: layer.frame.height / 2, bottom: 5, trailing: layer.frame.height / 2)
self.configuration = btnConfig
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
//Gradient
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = gradientColors.map { $0.cgColor }
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
gradientLayer.cornerRadius = layer.frame.height / 2
if let oldValue = layer.sublayers?[0] as? CAGradientLayer {
layer.replaceSublayer(oldValue, with: gradientLayer)
} else {
layer.insertSublayer(gradientLayer, below: nil)
}
//Shadow
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.frame.height / 2).cgPath
layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
layer.shadowOpacity = 0.7
layer.shadowRadius = 2.0
}
}
You can use UIButton Extension or refer code
Pass colors in array with start & end point of gradient effect, you want to start & end. i.e. x=0, y=0 means TopLeft & x=1, y=1 means BottomRight
extension UIButton {
func setGradientLayer(colorsInOrder colors: [CGColor], startPoint sPoint: CGPoint = CGPoint(x: 0, y: 0.5), endPoint ePoint: CGPoint = CGPoint(x: 1, y: 0.5)) {
let gLayer = CAGradientLayer()
gLayer.frame = self.bounds
gLayer.colors = colors
gLayer.startPoint = sPoint
gLayer.endPoint = ePoint
gLayer.cornerRadius = 5
gLayer.shadowOpacity = 0.8
gLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
layer.insertSublayer(gLayer, at: 0)
}
}
I want to replicate the Apple Map App Buttons as seen here:
This is the code so far:
class CustomView: UIImageView {
init(frame: CGRect, corners: CACornerMask, systemName: String) {
super.init(frame: frame)
self.createBorders(corners: corners)
self.createImage(systemName: systemName)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createBorders(corners: CACornerMask) {
self.contentMode = .scaleAspectFill
self.clipsToBounds = false
self.layer.cornerRadius = 20
self.layer.maskedCorners = corners
self.layer.masksToBounds = false
self.layer.shadowOffset = .zero
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowRadius = 20
self.layer.shadowOpacity = 0.2
self.backgroundColor = .white
let shadowAmount: CGFloat = 2
let rect = CGRect(x: 0, y: 2, width: self.bounds.width + shadowAmount * 0.1, height: self.bounds.height + shadowAmount * 0.1)
self.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
func createImage(systemName: String) {
let image = UIImage(systemName: systemName)!
let renderer = UIGraphicsImageRenderer(bounds: self.frame)
let renderedImage = renderer.image { (_) in
image.draw(in: frame.insetBy(dx: 30, dy: 30))
}
And use it like this:
let size = CGSize(width: 100, height: 100)
let frame = CGRect(origin: CGPoint(x: 100, y: 100), size: size)
let infoView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], systemName: "info.circle")
let locationView = CustomView(frame: frame, corners: [], systemName: "location")
let twoDView = CustomView(frame: frame, corners: [.layerMinXMaxYCorner, .layerMaxXMaxYCorner], systemName: "view.2d")
let binocularsView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner], systemName: "binoculars.fill")
let stackView = UIStackView(arrangedSubviews: [infoView, locationView, twoDView, binocularsView])
stackView.frame = CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 100, height: 400))
stackView.distribution = .fillEqually
stackView.axis = .vertical
stackView.setCustomSpacing(20, after: twoDView)
view.addSubview(stackView)
Which makes it look like this:
AND: There aren't the thin lines between each Icon of the stackView :/
Thanks for your help!
Not sure what's going on, because when I ran you code as-is I did not get the stretched images you've shown.
To get "thin lines between each Icon", you could either modify your custom image view to add the lines to the middle icon image, or, what might be easier, would be to use a 1-point height UIView between each icon.
Also, you'll be better off using auto-layout.
See if this gives you the desired result:
class CustomView: UIImageView {
init(frame: CGRect, corners: CACornerMask, systemName: String) {
super.init(frame: frame)
self.createBorders(corners: corners)
self.createImage(systemName: systemName)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createBorders(corners: CACornerMask) {
self.contentMode = .scaleAspectFill
self.clipsToBounds = false
self.layer.cornerRadius = 20
self.layer.maskedCorners = corners
self.layer.masksToBounds = false
self.layer.shadowOffset = .zero
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowRadius = 20
self.layer.shadowOpacity = 0.2
self.backgroundColor = .white
let shadowAmount: CGFloat = 2
let rect = CGRect(x: 0, y: 2, width: self.bounds.width + shadowAmount * 0.1, height: self.bounds.height + shadowAmount * 0.1)
self.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
func createImage(systemName: String) {
guard let image = UIImage(systemName: systemName)?.withTintColor(.blue, renderingMode: .alwaysOriginal) else { return }
let renderer = UIGraphicsImageRenderer(bounds: self.frame)
let renderedImage = renderer.image { (_) in
image.draw(in: frame.insetBy(dx: 30, dy: 30))
}
self.image = renderedImage
}
}
class TestCustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
let size = CGSize(width: 100, height: 100)
let frame = CGRect(origin: .zero, size: size)
let infoView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], systemName: "info.circle")
let locationView = CustomView(frame: frame, corners: [], systemName: "location")
let twoDView = CustomView(frame: frame, corners: [.layerMinXMaxYCorner, .layerMaxXMaxYCorner], systemName: "view.2d")
let binocularsView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner], systemName: "binoculars.fill")
// create 2 separator views
let sep1 = UIView()
sep1.backgroundColor = .blue
let sep2 = UIView()
sep2.backgroundColor = .blue
let stackView = UIStackView(arrangedSubviews: [infoView, sep1, locationView, sep2, twoDView, binocularsView])
// use .fill, not .fillEqually
stackView.distribution = .fill
stackView.axis = .vertical
stackView.setCustomSpacing(20, after: twoDView)
view.addSubview(stackView)
// let's use auto-layout
stackView.translatesAutoresizingMaskIntoConstraints = false
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// only need top and leading constraints for the stack view
stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 100.0),
stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 100.0),
// both separator views need height constraint of 1.0
sep1.heightAnchor.constraint(equalToConstant: 1.0),
sep2.heightAnchor.constraint(equalToConstant: 1.0),
])
// set all images to the same 1:1 ratio size
[infoView, locationView, twoDView, binocularsView].forEach { v in
v.widthAnchor.constraint(equalToConstant: size.width).isActive = true
v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
}
}
}
This is what I get with that code example:
I am adding a custom view to another view who then has its constraints set programatically. This results in the content of the view being drawn in the wrong place, and I'm not sure why. Here's the code I have and what I tried so far:
// Called in viewDidLoad
let buttonView = UIView()
buttonView.translatesAutoresizingMaskIntoConstraints = false
let ring = CircularProgressView()
ring.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
ring.backgroundColor = .yellow
buttonView.addSubview(ring)
self.view.addSubview(buttonView)
NSLayoutConstraint.activate([
buttonView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
buttonView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -28),
buttonView.heightAnchor.constraint(equalToConstant: 80),
buttonView.widthAnchor.constraint(equalToConstant: 80),
ring.centerXAnchor.constraint(equalTo: buttonView.centerXAnchor),
ring.centerYAnchor.constraint(equalTo: buttonView.centerYAnchor)
])
I tried calling ring.layoutIfNeeded() or setting the width and height of the ring view with constraints, and it didn't change anything.
ring.heightAnchor.constraint(equalToConstant: 80),
ring.widthAnchor.constraint(equalToConstant: 80)
The CircularProgressView class looks like this. I tried using bounds instead of frame here or not halving the values by 2 and again no change.
class CircularProgressView: UIView {
// First create two layer properties
private var circleLayer = CAShapeLayer()
private var progressLayer = CAShapeLayer()
private var currentValue: Double = 0
override init(frame: CGRect) {
super.init(frame: frame)
createCircularPath()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
createCircularPath()
}
func createCircularPath() {
let circularPath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0),
radius: 41,
startAngle: -.pi / 2,
endAngle: 3 * .pi / 2,
clockwise: true)
circleLayer.path = circularPath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.lineCap = .round
circleLayer.lineWidth = 3.0
circleLayer.strokeColor = UIColor.black.withAlphaComponent(0.2).cgColor
progressLayer.path = circularPath.cgPath
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.lineCap = .round
progressLayer.lineWidth = 3.0
progressLayer.strokeEnd = 0
progressLayer.strokeColor = UIColor.black.cgColor
layer.addSublayer(circleLayer)
layer.addSublayer(progressLayer)
let circularProgressAnimation = CABasicAnimation(keyPath: "strokeEnd")
circularProgressAnimation.duration = 2
circularProgressAnimation.toValue = 1
circularProgressAnimation.fillMode = .forwards
circularProgressAnimation.isRemovedOnCompletion = false
progressLayer.add(circularProgressAnimation, forKey: "progressAnim")
progressLayer.pauseAnimation()
}
}
This all works with no issue if done via the storyboard, but I need to create it programatically for this use case, and I'm a little confused about what's wrong.
You need to make both views programmatically like
// Called in viewDidLoad
let buttonView = UIView()
buttonView.translatesAutoresizingMaskIntoConstraints = false
let ring = CircularProgressView()
ring.translatesAutoresizingMaskIntoConstraints = false
ring.backgroundColor = .yellow
buttonView.addSubview(ring)
self.view.addSubview(buttonView)
NSLayoutConstraint.activate([
buttonView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
buttonView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -28),
buttonView.heightAnchor.constraint(equalToConstant: 80),
buttonView.widthAnchor.constraint(equalToConstant: 80),
ring.centerXAnchor.constraint(equalTo: buttonView.centerXAnchor),
ring.centerYAnchor.constraint(equalTo: buttonView.centerYAnchor),
ring.heightAnchor.constraint(equalToConstant: 80),
ring.widthAnchor.constraint(equalToConstant: 80)
])
and Call createCircularPath only inside layoutSubviews
override func layoutSubviews() {
super.layoutSubviews()
createCircularPath()
}
Try to also set CircularProgressView's translatesAutoresizingMaskIntoConstraints property to false:
ring.translatesAutoresizingMaskIntoConstraints = false
I have created a messaging app and I want to set the border colour as a gradient instead of just a solid colour.
So far when I run my code this is what is see:
The gradient colour is supposed to be a border colour for each message cell
I don't know what is making it look the way it does
this is how I coded it :
I've created an extension to deal with the gradient and it looks like this:
extension UIView {
func gradientButton( startColor:UIColor, endColor:UIColor) {
let view:UIView = UIView(frame: self.bounds)
let gradient = CAGradientLayer()
gradient.colors = [startColor.cgColor, endColor.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.frame = self.bounds
self.layer.insertSublayer(gradient, at: 0)
self.mask = view
view.layer.borderWidth = 2
}
}
and when I set the gradient I set it like this in my chatController class
class ChatController: UICollectionViewController, UICollectionViewDelegateFlowLayout, ChatCellSettingsDelegate {
func configureMessage(cell: ChatCell, message: Message) {
cell.bubbleView.gradientButton(startColor: blue, endColor: green)
}
The bubbleView holds the text which is a textView of the message which is created in my ChatCell class:
class ChatCell: UICollectionViewCell {
let bubbleView: UIView = {
let bubble = UIView()
bubble.backgroundColor = UIColor.rgb(red: 0, green: 171, blue: 154, alpha: 1)
bubble.translatesAutoresizingMaskIntoConstraints = false
bubble.layer.masksToBounds = true
bubble.layer.cornerRadius = 13
bubble.backgroundColor = .white
return bubble
}()
let textView: UITextView = {
let text = UITextView()
text.text = "test"
text.font = UIFont.systemFont(ofSize: 16)
text.backgroundColor = .clear
text.translatesAutoresizingMaskIntoConstraints = false
text.isEditable = false
return text
}()
}
how do I fix this?
any help would be helpful
thank you
I can set the border colour to a solid colour with the following line:
cell.bubbleView.layer.borderColor = UIColor.rgb(red: 91, green: 184, blue: 153, alpha: 0.8).cgColor
and then it would look like this:
You can create your own bordered view and use it in your custom cell:
import UIKit
import PlaygroundSupport
public class GradientBorder: UIView {
var startColor: UIColor = .black
var endColor: UIColor = .white
var startLocation: Double = 0.05
var endLocation: Double = 0.95
var path: UIBezierPath!
let shape = CAShapeLayer()
var lineWidth: CGFloat = 5
override public class var layerClass: AnyClass { CAGradientLayer.self }
var gradientLayer: CAGradientLayer { layer as! CAGradientLayer }
func update() {
gradientLayer.startPoint = .init(x: 0.0, y: 0.5)
gradientLayer.endPoint = .init(x: 1.0, y: 0.5)
gradientLayer.locations = [startLocation as NSNumber, endLocation as NSNumber]
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
path = .init(roundedRect: bounds.insetBy(dx: lineWidth/2, dy: lineWidth/2), byRoundingCorners: [.topLeft, .bottomLeft, .topRight, .bottomRight], cornerRadii: CGSize(width: frame.size.height / 2, height: frame.size.height / 2))
shape.lineWidth = lineWidth
shape.path = path.cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradientLayer.mask = shape
}
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
update()
}
}
Playground testing:
let gradientBorderedView = GradientBorder()
gradientBorderedView.frame = .init(origin: .zero, size: .init(width: 200, height: 40))
let view = UIView()
view.frame = .init(origin: .zero, size: .init(width: 375, height: 812))
view.backgroundColor = .blue
view.addSubview(gradientBorderedView)
PlaygroundPage.current.liveView = view
I just want to draw a dash line around some view coding with VFL. Here is my demo code
var ZLDashLineLayerInView = "com.siamLian.ZLDashLineLayerInView"
extension UIView {
convenience init(frame:CGRect = CGRectZero, lineDashPattern: [NSNumber], strokeColor: UIColor?, fillColor: UIColor? = UIColor.clearColor(), lineWidth: CGFloat = 1, roundingCorners: UIRectCorner = .AllCorners, cornerRadii: CGSize) {
let ori_Method = class_getInstanceMethod(UIView.self, "drawRect:")
let my_Method = class_getInstanceMethod(UIView.self, "ZLDrawRect:");
method_exchangeImplementations(ori_Method, my_Method)
self.init(frame: frame)
let shapeLayer = CAShapeLayer()
shapeLayer.lineDashPattern = lineDashPattern
shapeLayer.strokeColor = strokeColor?.CGColor
shapeLayer.fillColor = fillColor?.CGColor
shapeLayer.lineWidth = lineWidth
let path = UIBezierPath(roundedRect: frame, byRoundingCorners: roundingCorners, cornerRadii: cornerRadii)
shapeLayer.path = path.CGPath
self.layer.addSublayer(shapeLayer)
//objc_setAssociatedObject(self, &ZLDashLineLayerInView, shapeLayer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
//self.addObserver(self, forKeyPath: "bounds", options: NSKeyValueObservingOptions.New, context: nil)
self.setNeedsDisplay()
}
}
init view like this :
let label = UILabel(frame: CGRectMake(0, 0, 0, 0), lineDashPattern: [NSNumber(integer: 7), NSNumber(integer: 7)], strokeColor: UIColor.redColor(), lineWidth: 1, cornerRadii: CGSizeMake(10, 0))
label.text = "sssssssssssss"
label.center = self.view.center
self.view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-100-[label]-100-|", options: NSLayoutFormatOptions.AlignAllCenterX, metrics: nil, views: ["label" : label]))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-100-[label]-100-|", options: NSLayoutFormatOptions.AlignAllCenterY, metrics: nil, views: ["label" : label]))
Because of the label's frame, I init the shapelayer.path with wrong frame, so when should i update the shapelayer's path?
Note:
I know i can custom a view and override the func
func drawRect(_ rect: CGRect)
I don't want to custom view, any answers appreciated