ImageView Gradient behaving abnormally - ios

I have gradient(clear - black) applied to an imageview and on top of it I have a float button. Everything works fine. The only issue is, whenever there is a tap on float button, the gradient start increasing to black more and more. my gradient is clear to black from top to bottom. But on interaction, It start to slowely blacken towards upside.
I am really unable to solve this error.
This image view is in a UIcollectionResuableView. Below is the code for yhe following.
func addBlackGradientLayerprof(frame: CGRect){
let gradient = CAGradientLayer()
gradient.frame = frame
let black = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.65).cgColor
gradient.colors = [UIColor.clear.cgColor, black]
gradient.locations = [0.0, 1.0]
self.layer.addSublayer(gradient)
}
Header View with floating button:
override func layoutSubviews() {
super.layoutSubviews()
profilePic.addBlackGradientLayerprof(frame: profilePic.bounds)
}
override func awakeFromNib() {
super.awakeFromNib()
layoutFAB()
}
func layoutFAB() {
floaty.openAnimationType = .slideDown
floaty.addItem("New Post", icon: UIImage(named: "photo-camera")) { item in
self.delegate.fabUploadClicked()
}
floaty.addItem("Settings", icon: UIImage(named: "settingsB")) { item in
self.delegate.fabSettingsClicked()
}
floaty.paddingY = (frame.height - 30) - floaty.frame.height/2
floaty.fabDelegate = self
floaty.buttonColor = UIColor.white
floaty.hasShadow = true
floaty.size = 45
addSubview(floaty)
}

layoutSubviews is bad place to do anything other than adjust a few frames as needed. It can be called many times in the lifecycle of a view. You should not be adding layers from layoutSubviews.
You should call addBlackGradientLayerprof from a place guaranteed to only be called once in the lifetime of the object. awakeFromNib would be one possible place.

Related

How to properly generate a random gradient background?

I've been trying to create a function that generates a random gradient background every time a new view controller is presented, as shown with the code provided below, yet I'm unable to get it to properly work. The issue being that the "random" colors are always the same and are generated in the same order.
import Foundation
import UIKit
extension UIView {
func setGradientBackground() {
let redValue = CGFloat(drand48())
let greenValue = CGFloat(drand48())
let blueValue = CGFloat(drand48())
let redValue2 = CGFloat(drand48())
let greenValue2 = CGFloat(drand48())
let blueValue2 = CGFloat(drand48())
let random = UIColor(red: redValue, green: greenValue, blue: blueValue, alpha: 1).cgColor
let random2 = UIColor(red: redValue2, green: greenValue2, blue: blueValue2, alpha: 1).cgColor
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = [random, random2]
return layer.insertSublayer(gradient, at: 0)
}
}
If you use drand48, you need to seed it (e.g. with srand48). The quick and dirty solution would be to seed it with some value derived from time from the number of seconds passed since some reference date or the like.
Even better, I’d suggest using CGFloat.random(in: 0...1) instead, which doesn’t require any seeding.
extension UIView {
func addGradientBackground() {
let random = UIColor.random().cgColor
let random2 = UIColor.random().cgColor
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = [random, random2]
layer.insertSublayer(gradient, at: 0)
}
}
extension UIColor {
static func random() -> UIColor {
let red = CGFloat.random(in: 0...1)
let green = CGFloat.random(in: 0...1)
let blue = CGFloat.random(in: 0...1)
return UIColor(red: red, green: green, blue: blue, alpha: 1)
}
}
For what it’s worth, we should appreciate that this technique of adding a sublayer is problematic if the view changes size (e.g., you rotate the device, etc.). You’d have to manually hook into viewDidLayoutSubviews or layoutSubviews and adjust this gradient layer frame. I might instead suggest actually having a UIView subclass that does this gradient layer.
#IBDesignable
class RandomGradientView: UIView {
override static var layerClass: AnyClass { return CAGradientLayer.self }
var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
gradientLayer.colors = [UIColor.blue.cgColor, UIColor.red.cgColor]
}
}
private extension RandomGradientView {
func configure() {
let random = UIColor.random().cgColor
let random2 = UIColor.random().cgColor
gradientLayer.colors = [random, random2]
}
}
Because this UIView subclass actually specifies the layer class to be used for the main backing layer, and because that main layer always resizes automatically as the view changes size, then it will gracefully handle any size changes (including animated ones).
All of the having been said, I appreciate the convenience of the extension (you can apply it to existing UIView subclasses), but you have to offset that with the more cumbersome process of handling frame size changes. It’s your choice, but I wanted to provide the alternative.

Change the colors of CAGradientLayer with slider in realtime

I want to be able to change to change the colours of a CAGradientLayer in realtime with values i receive over #IBAction in the main ViewController. UISlider moves, color changes in background. I m asking for a theoretical approach.
What is the pattern or technique used to achieve this ? i really got lost and the mighty internet didn't reveal any usable help.
Subclasssing a UIView and add the gradientLayer there ? Then observe the variable with the incoming values and let KVO update the array entries (color sets) with cgColor values ?
Instantiate the gradientLayer in the main ViewController and update its color Properties there when value changes come in via #IBAction ?
Code helps, but its secondary here. i am asking more for a theoretical solution. I try to follow MVC but i am hardly confused where the gradientLayer should be instantiated, whats the best method to change the colours dynamically ect…
open for inputs, thx
Using a very simple extension for UIView you can get what you need. Here is a quick example using semi-static colors just for simplicity, but you can extend it as you need.
extension UIView {
/**
Adds colors to a CAGradient Layer.
- Parameter value: The Float coming from the slider.
- Parameter gradientLayer: The CAGradientLayer to change the colors.
*/
func addColorGradient (value: CGFloat, gradientLayer: CAGradientLayer) {
let topColor = UIColor(red: value / 255, green: 1, blue: 1, alpha: 1)
let bottomColor = UIColor(red: 1, green: 1, blue: value / 255, alpha: 1)
gradientLayer.colors = [topColor.cgColor, bottomColor.cgColor]
}
}
Usage example:
import UIKit
class ViewController: UIViewController {
let slider : UISlider = {
let slider = UISlider(frame: .zero)
slider.maximumValue = 255
slider.minimumValue = 0.0
return slider
}()
var gradientLayer = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(slider)
slider.translatesAutoresizingMaskIntoConstraints = false
slider.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
slider.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
slider.widthAnchor.constraint(equalToConstant: 200).isActive = true
slider.heightAnchor.constraint(equalToConstant: 20).isActive = true
slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
gradientLayer.bounds = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, at: 0)
}
#objc func sliderValueChanged(sender: UISlider) {
let currentValue = CGFloat(sender.value)
self.view.addColorGradient(value: currentValue, gradientLayer: gradientLayer)
}
}
Just change the topColor and bottomColor as you need and manipulate their values with the slider. Hope it helps
class ViewController: UIViewController {
var gradientLayer = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
gradientLayer.bounds = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, at: 0)
}
// Slider Input
#IBAction func sliderValueChanged(_ sender: UISlider) {
self.view.addColorGradient(value: CGFloat(sender.value), gradientLayer: gradientLayer)
}
}

iOS - How to apply gradient color to UICollectionViewCell backgroundView?

I have been able to apply the gradient I want to the backgroundView of my UICollectionViewCell. But every time I reload the cell, it applies again my gradient, accumulating all the gradient.
First of all here is my method for the gradient :
static func setGradientWhite(uiView: UIView) {
let colorBottomWhite = UIColor(red:1.00, green:1.00, blue:1.00, alpha:0.30).cgColor
let colorTopWhite = UIColor(red:1.00, green:1.00, blue:1.00, alpha:0).cgColor
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [ colorTopWhite, colorBottomWhite]
gradientLayer.locations = [ 0.0, 1.0]
gradientLayer.frame = uiView.bounds
uiView.layer.insertSublayer(gradientLayer, at: 0)
}
I know I can do it in a more generic way, and change the way I am handling the colors, but it's not the point, it is for testing purpose for now.
I tried to call it like this :
override func awakeFromNib() {
super.awakeFromNib()
UIUtils.setGradientWhite(uiView: self)
}
The gradients do not accumulate this way, in the awakeFromNib, BUT, the animation isn't finish and I only have half of my cell with gradient applied.
And if I do it in this method :
override func layoutSubviews() {
super.layoutSubviews()
CATransaction.begin()
CATransaction.setDisableActions(true)
UIUtils.setGradientWhite(uiView: self)
CATransaction.commit()
}
The gradient animation is properly finished, applying to the whole view, but when the cell is reloading, it applies a new gradient on top of the previous one, until I cannot see my cell anymore (in this case too much white here).
Any ideas on how can I solve this issue?
You can try
override func layoutSubviews() {
super.layoutSubviews()
if !(self.layer.sublayers?.first is CAGradientLayer) {
CATransaction.begin()
CATransaction.setDisableActions(true)
UIUtils.setGradientWhite(uiView: self)
CATransaction.commit()
}
}

Add Facebook Shimmer on multiple UIViews

I am trying to add Facebook Shimmer on UICollectionViewCell which has multiple UIViews.
For one UIView, it's working fine with below code:
let shimmeringView = FBShimmeringView(frame: imageView.frame)
shimmeringView.contentView = imageView
backgroundView.addSubview(shimmeringView)
shimmeringView.isShimmering = true
Where backgroundView is the view in which I have all the subviews such as imageView, labelView and others.
While I am trying to add multiple views then first view is getting correct frame but other views' widths are becoming zero. I'm adding this code inside collectionView(_:cellForItemAt:).
let shimmeringView = FBShimmeringView(frame: imageView.frame)
shimmeringView.contentView = imageView
backgroundView.addSubview(shimmeringView)
shimmeringView.isShimmering = true
let shimmeringView = FBShimmeringView(frame: labelView.frame)
shimmeringView.contentView = labelView
backgroundView.addSubview(shimmeringView)
shimmeringView.isShimmering = true
Can anyone tell me if it's the correct way to implement Facebook Shimmer for multiple UIViews or Where I am doing it wrong?
I believe there are many ways to implement FBShimmeringView, it's a matter of preferences. So in my case, I prefer the easiest way (according to me).
What I do in my tableViewCell that has of course multiple views such as imageView and labels, just like yours, is that I have multiple UIView gray color, placed on top of each views in my cell.
Then I only have ONE instance of FBShimmeringView added to my cell.
Here are some more details about what I practice for using FBShimmeringView.
*Take note that I use SnapKit to layout my views programmatically.
I have a property in my cell called isLoading like so, which determines if the gray colored views should be shown or now. If shown, of course turn on shimmering:
public var serviceIsLoading: Bool = false {
didSet {
_ = self.view_Placeholders.map { $0.isHidden = !self.serviceIsLoading }
self.view_Shimmering.isHidden = !self.serviceIsLoading
self.view_Shimmering.isShimmering = self.serviceIsLoading
}
}
Then I add a white view to my cell after adding all the subviews to the cell:
// Place the FBShimmeringView
// Try to add a dummy view
let dummyView = UIView()
dummyView.backgroundColor = .white
self.addSubview(dummyView)
dummyView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
Add the ShimerringView to the cell as well:
self.addSubview(self.view_Shimmering)
self.view_Shimmering.snp.makeConstraints { (make) in
make.height.width.equalToSuperview()
make.center.equalToSuperview()
}
Finally, make the dummyView as the contentView of the cell:
self.view_Shimmering.contentView = dummyView
My screen would look like this. Also remember to disable interaction in your tableView.
This looks cool to me when it shimmers, just one shimerring view.
Hope it helps!
Below extension is working fine.
extension UIView {
func startShimmeringViewAnimation() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
let gradientColorOne = UIColor(white: 0.90, alpha: 1.0).cgColor
let gradientColorTwo = UIColor(white: 0.95, alpha: 1.0).cgColor
gradientLayer.colors = [gradientColorOne, gradientColorTwo, gradientColorOne]
gradientLayer.locations = [0.0, 0.5, 1.0]
self.layer.addSublayer(gradientLayer)
let animation = CABasicAnimation(keyPath: "locations")
animation.fromValue = [-1.0, -0.5, 0.0]
animation.toValue = [1.0, 1.5, 2.0]
animation.repeatCount = .infinity
animation.duration = 1.25
gradientLayer.add(animation, forKey: animation.keyPath)
}
}
In UITableViewCell class, we need to add the Shimmer for each views.
class UAShimmerCell: UITableViewCell {
#IBOutlet weak var thumbNailView: UIView!
#IBOutlet weak var label1View: UIView!
#IBOutlet weak var label2View: UIView!
override func awakeFromNib() {
super.awakeFromNib()
thumbNailView.startShimmeringViewAnimation()
label1View.startShimmeringViewAnimation()
label2View.startShimmeringViewAnimation()
}
}

How to set up UIBezierPath border for view in ios?

I created CAShapeLayer border for view using the below code:
func addBorderToWebView() {
borderShape = CAShapeLayer()
borderShape.path = UIBezierPath(rect: webView.frame).cgPath
borderShape.lineWidth = CGFloat(2)
borderShape.bounds = borderShape.path!.boundingBox
borderShape.strokeColor = UIColor(red: 68/255, green: 219/255, blue: 94/255, alpha: 1.0).cgColor
borderShape.position = CGPoint(x: borderShape.bounds.width/2, y: borderShape.bounds.height/2)
webView.layer.addSublayer(borderShape)
}
I right and bottom border lines are out of the frame, you can see it on the image below. However, if I define borderWidth property of webView.layer, everything appears to be showed fine. That is why I make a conclusion, that the problem is not in layout constraints. How should I configure UIBezierPath (or CAShapeLayer) to make the border be showed correctly? Thank you for any help!
Try to replace this
borderShape.path = UIBezierPath(rect: webView.frame).cgPath
with this:
borderShape.path = UIBezierPath(rect: webView.bounds).cgPath
Another possible cause of the problem is that webView's frame is not updated yet when you set it to borderShape. You can try to do self.view.updateLayoutIfNeeded() prior to adding border shape. If it doesn't help override the following method:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updateBorderShape()
}
func updateBorderShape() {
borderShape.bounds = webView.bounds
borderShape.position = CGPoint(x: borderShape.bounds.width/2, y: borderShape.bounds.height/2)
}
This will make sure that every time your frames are updated, border shape is updated as well.

Resources