Gradient mask on UIScrollView slow to update - swift

I have a UIScrollView subclass containing a very wide UIView subclass, and I want to fade out the edges of the visible content. Here's what I've got in the UIScrollView subclass:
private var gradientMask = CAGradientLayer()
override func layoutSubviews() {
super.layoutSubviews()
gradientMask.frame = self.bounds
gradientMask.colors = [UIColor(white: 0.0, alpha: 0.18).CGColor,
UIColor(white: 0.0, alpha: 0.95).CGColor,
UIColor(white: 0.0, alpha: 0.95).CGColor,
UIColor(white: 0.0, alpha: 0.0).CGColor
]
let fadeStart:CGFloat = 50.0 / self.bounds.width
gradientMask.startPoint = CGPoint(x:0, y:0.5)
gradientMask.endPoint = CGPoint(x:1, y:0.5)
let fadeEnd = (self.bounds.width - 40.0) / self.bounds.width
gradientMask.locations = [fadeStart,fadeStart+0.1,fadeEnd,1.0]
self.layer.mask = gradientMask
}
It does work, but there's a lag. After you scroll, the gradient appears to scroll with the content for a moment then snaps back to the expected position. How can I ensure the edges of the scroll view fade out—and that the gradient doesn't move when scrolling?

The fix was just to embed the UIScrollView subclass into a view that has the gradient applied. That's it!

Related

Why are UIViews background color is not updating?

Switch statement works but won't reset view background colours etc.
I have a UIImage (icon) and a UIButton embedded within a UIView (of custom type DropShadowCircleView) as per image below.
When the walking button is tapped a var navigationOption is set to either walking or driving and setupNavigationSelectionView() is executed.
Problem is: case "walking" of the switch works perfectly, but case "driving" doesn't reset the UIView and icon tint color witcback to their original setting eg; background color etc.. any ideas why?
func setupNavigationSelectionView(){
switch navigationOption {
case "walking":
walkingBg.setGradientBackground(colourOne: softGreen, ColourTwo: softBlue)
walkingBg.layer.cornerRadius = walkingBg.frame.width / 2
walkingBg.clipsToBounds = true
walkingIcon.tintColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
case "driving":
walkingBg.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
walkingBg.layer.cornerRadius = walkingBg.frame.width / 2
walkingBg.clipsToBounds = true
walkingIcon.tintColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
default:
break
}
}
EDIT: this is my DropShadowCircleView class
class DropShadowCircleView: UIView {
override func awakeFromNib() {
setupView()
super.awakeFromNib()
}
func setupView(){
self.layer.shadowOpacity = 0.50
self.layer.shadowRadius = 20
self.layer.shadowColor = UIColor.black.cgColor
self.layer.cornerRadius = self.frame.width / 2
}
}
EDIT: This is my setGradientBackground function which is within an extension file to UIView
func setGradientBackground(colourOne: UIColor, ColourTwo: UIColor) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [colourOne.cgColor, ColourTwo.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.0)
layer.insertSublayer(gradientLayer, at: 0)
}
You need to remove your gradient layer when you reset your icon.
Add this to your extension UIView:
func removeGradientBackground() {
guard
let idx = layer.sublayers?.index(where: { $0 is CAGradientLayer })
else { return }
layer.sublayers?.remove(at: idx)
}
and call it when you are resetting your icon.

Reuse shadow CALayer UITableViewCell

I am trying to create a drop shadow for my UITableViewCell's but the CALayer doesn't work like it suppose.
Once it's only on the top of the cell.
Once it's only on the bottom of the cell.
Once it's on the bottom and top of the cell.
My custom cell have :
Global variable:
var l = CALayer()
Next as a cell is going to be reused I have to remove the layer from the super layer.
override func prepareForReuse() {
super.prepareForReuse()
l.removeFromSuperlayer()
}
I am setting a bounds and inserting the layer inside :
override func layoutSubviews() {
super.layoutSubviews()
l.frame = self.bounds
layer.insertSublayer(l, at: 0)
}
At the end layer configurations are inside willDisplay cell:
l.backgroundColor = UIColor.rgbColor(red: 221, green: 221, blue: 221, alpha: 1.0).cgColor
l.position = CGPoint(x: bounds.width / 2, y: -bounds.height / 2 + 0.5)
l.bounds = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height)
l.shadowColor = UIColor.darkGray.cgColor
l.shadowOffset = CGSize(width: 0, height: 1)
l.shadowOpacity = 0.8
l.shadowRadius = 5.0
l.zPosition = -1
l does not have cell. before becouse it is all going inside Custom cell methods.
Any ideas what am I missing ?
Thanks!

Set gradient on UIView partially, half color is gradient and half is single color

Trying to show the progress bar made it custom, took an UIView set it frame to percent of progress. Say 20% of frame width and made gradient but remaining 80% should be white color and text on it defining percentage.
Problems facing is not able to display text set UILabel instead of UIView but text not displaying. Please guide.
Below is what i have tried.
let view: UILabel = UILabel(frame: CGRectMake(0.0, self.scrollMainView.frame.size.height-50, self.view.frame.size.width/5, 50))
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = view.bounds
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
let color0 = UIColor(red:71.0/255, green:198.0/255, blue:134.0/255, alpha:1.0).CGColor
let color1 = UIColor(red:25.0/255, green:190.0/255, blue: 205.0/255, alpha:1.0).CGColor
// let color2 = UIColor(red:0.0/255, green:0.0/255, blue: 0.0/255, alpha:1.0).CGColor
gradient.colors = [color1, color0]
view.layer.insertSublayer(gradient, atIndex: 0)
self.scrollMainView.addSubview(view)
view.text = "20%"
view.textColor = UIColor.blackColor()
view.layer.shadowColor = UIColor.blackColor().CGColor
view.layer.shadowOpacity = 1
view.layer.shadowOffset = CGSizeZero
view.layer.shadowRadius = 2
I cant understand your problem from your question exactly but I will answer based on question title only. To get half color gradient and half single color you have to use three colors gradient and set their locations accordingly :
gradient.colors = [color1,color1, color0]
gradient.locations = [0.0, 0.5, 1.0]
This way you will draw a gradient from color1 to color1 (which in fact is single color) and fill 50% of frame's area with it and a gradient from color1 to color0 that will fill other half of the frame.
I answer to why you can't see the text.
Forget for one second layers and think about subviews. What should happen if you add a subview to a UILabel? It will be on top of the label's content, of course.
So, the same applies to layers. The UILabel draws its text on its main layer, and any sublayer you add to the main layer is on top of it.
My suggestion is to use a UIView with a CAGradientLayer sublayer and a UILabel subview.
Or even better, subclass a UIView in order to use a CAGradientLayer as backing layer (through class func layerClass() -> AnyClass method) and just add UILabel as subview.
Here is an example:
class CustomView : UIView {
lazy var label : UILabel = {
let l = UILabel(frame: self.bounds)
l.text = "20%"
l.textColor = UIColor.blackColor()
l.backgroundColor = UIColor.clearColor()
return l
}()
override class func layerClass() -> AnyClass {
return CAGradientLayer.self
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
let gradient: CAGradientLayer = self.layer as! CAGradientLayer
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
let color0 = UIColor(red:71.0/255, green:198.0/255, blue:134.0/255, alpha:1.0).CGColor
let color1 = UIColor(red:25.0/255, green:190.0/255, blue: 205.0/255, alpha:1.0).CGColor
gradient.colors = [color1, color0]
label.frame = bounds
label.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(label)
}
}

Swift: Gradient splits on rotation

In short, I have a gradient that is a mix of Dark Blue and Black. The gradient looks beautiful, however when I rotate the screen and put it in landscape, the two colors split and half of the screen has a blue background and the other half is black. Figuring I didn't do it right, I copied codes from these two sources:
YouTube video https://www.youtube.com/watch?v=pabNgxzEaRk
Website http://blog.apoorvmote.com/gradient-background-uiview-ios-swift/
This is my code:
let topColor = UIColor(red: 28/255.0, green: 25/255.0, blue: 127/255.0, alpha: 1)
let bottomColor = UIColor(red: 0/255.0, green: 0/255.0, blue: 25/255.0, alpha: 1)
let gradientColors: [CGColor] = [topColor.CGColor, bottomColor.CGColor]
let gradientLocations: [Float] = [0.0, 1.0]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, atIndex: 0)
Can someone point me in the right direction to stop my gradient from splitting in two?
Keep in mind that when changing screen orientation all view's bounds change as well (assuming you are using auto layout) but the same thing is not applied on programmatically added layers. So in your case gradient layer still has the frame which is set based on old view bounds before rotation. To solve this I suggest subclassing your gradient view and update gradient layer frame in layoutSubviews function which is called on each view bounds change.
class GradientView: UIView {
let gradientLayer = CAGradientLayer()
override func awakeFromNib() {
gradientLayer.frame = self.bounds
gradientLayer.colors = yourColors
gradientLayer.locations = yourLocations
self.layer.addSublayer(gradientLayer)
}
override func layoutSubviews() {
gradientLayer.frame = self.bounds
}
}
You only need to override layoutSubview method to Autoresize your gradient view with respect to your device's view
Write only,
override func viewDidLayoutSubviews() {
gradientLayer.frame = self.view.bounds
}
I tried this and its working fine. Hope this help you.

Swift function not drawing circle in updated view

I have a stepper in my View Controller that updates variables(redd1, greenn1, bluee1) in my UIView. drawRect draws an initial circle and updateColor is meant to draw a new circle on top of it with an updated color. The variables get updated when I call updateColor and I know that they get passed through because when i have their values printed out in updateColor they are correct. updateColor won't draw a new circle.
class UIView1: UIView {
var redd1 = 0.0;
var greenn1 = 0.0;
var bluee1 = 0.0;
override init(frame: CGRect)
{
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect)
{
let circle2 = UIView(frame: CGRect(x: -25.0, y: 10.0, width: 100.0, height:100.0))
circle2.layer.cornerRadius = 50.0
let startingColor2 = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat (greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle2.backgroundColor = startingColor2;
addSubview(circle2);
}
func updateColor()
{
let circle = UIView(frame: CGRect(x: -25.0, y: 10.0, width: 100.0, height: 100.0))
circle.layer.cornerRadius = 50.0;
let startingColor = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat(greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle.backgroundColor = startingColor;
addSubview(circle);
}
}
Do not call addSubview in drawRect. Only use drawRect if you were going to draw a circle yourself (e.g. by calling stroke of a UIBezierPath). Or if you're going to add circles as subviews (as CAShapeLayer sublayers to the view's layer), the retire drawRect altogether.
But by calling addSubview in updateColor, you're not changing the color, but rather you're adding another subview every time. Likewise, by calling addSubview in drawRect, you're adding another subview there, too, every time the OS calls drawRect. For example, I ran your code, changing the color from black to red, to green, to blue, and triggered drawRect to be called again a few more times, and when I look at the view hierarchy in the view debugger, you can see all of the views in the view hierarchy:
This is a dangerous practice, because those subviews will add up over time, taking up memory.
If you're going to add subviews, I would suggest (a) drawRect is not the right place to be adding subviews; and (b) if you're determined to go the addSubview approach, decide whether you can remove the previous subviews before adding new ones.
Personally, if I wanted drawRect to draw circles of different colors, I would just stroke the UIBezierPath. For example:
class CircleView: UIView {
var redd1: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
var greenn1 : CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
var bluee1: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
override func drawRect(rect: CGRect) {
let color = UIColor(red: redd1 / 255.0, green: greenn1 / 255.0, blue: bluee1 / 255.0, alpha: 1.0)
color.setStroke()
let path = UIBezierPath(arcCenter: CGPoint(x: 75, y: 75), radius: 50, startAngle: 0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
path.lineWidth = 2
path.lineCapStyle = .Round
path.stroke()
}
}
By the way, you notice that we can retire updateColor entirely. With the above code, if you set either red, green, or blue, it will call setNeedsDisplay, which will trigger the view being redrawn automatically.
You report that the above isn't working. Well, I tried it with this code:
class ViewController: UIViewController {
#IBOutlet weak var circleView: CircleView!
#IBOutlet weak var redSlider: UISlider!
#IBOutlet weak var greenSlider: UISlider!
#IBOutlet weak var blueSlider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
redSlider.value = Float(circleView.redd1) / 255.0
greenSlider.value = Float(circleView.greenn1) / 255.0
blueSlider.value = Float(circleView.bluee1) / 255.0
}
#IBAction func valueChangedForSlider(sender: UISlider) {
circleView.redd1 = 255.0 * CGFloat(redSlider.value)
circleView.greenn1 = 255.0 * CGFloat(greenSlider.value)
circleView.bluee1 = 255.0 * CGFloat(blueSlider.value)
}
}
And it worked fine:
As you mentioned that the both have the values which they should have.
The two circles which you are drawing are exactly on the same postion and both have exactly the same color according to your code snippet. How should they differ? Of course you can't see the two circles when they are having the same color and same position. They are overlayed.