Add Facebook Shimmer on multiple UIViews - ios

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()
}
}

Related

How to animate the shadow border of an UIView at the same time its content grows

I am working on a project which has all views embedded in a view container like this:
Today, I have to make one of these containers grow vertically in a animatable way when a stack view is showing its arranged subviews. So, the first problem was that the shadow border of this container is not being animated and I realized that the shadow path should be animated in another way and not just in a UIView.animate block.
I have isolated the problem in a small project:
As you can see, I am trying to deploy/undeploy the container view depending on the inner stackview arranged subviews. The problem is the shadow path, which I am already trying to understand how it works.
Can you help me to understand how to manage a UIView shadow layer when you need to update the height of an UIView in an animation block?
Container Shadow view code:
import UIKit
#IBDesignable final class ShadowView: UIView {
#IBInspectable var cornerRadius: CGFloat = 0.0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowColor: UIColor = UIColor.darkGray {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowOffsetWidth: CGFloat = 0.0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowOffsetHeight: CGFloat = 1.8 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowOpacity: Float = 0.30 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowRadius: CGFloat = 3.0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var isAnimatable: Bool = false
private var shadowLayer: CAShapeLayer = CAShapeLayer() {
didSet {
setNeedsLayout()
}
}
private var previousPat: CGPath = CGPath(rect: CGRect(x: 0, y: 0, width: 0, height: 0), transform: .none)
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = cornerRadius
shadowLayer.path = UIBezierPath(roundedRect: bounds,
cornerRadius: cornerRadius).cgPath
shadowLayer.fillColor = backgroundColor?.cgColor
shadowLayer.shadowColor = shadowColor.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: shadowOffsetWidth,
height: shadowOffsetHeight)
shadowLayer.shadowOpacity = shadowOpacity
shadowLayer.shadowRadius = shadowRadius
if isAnimatable {
let animation = CABasicAnimation(keyPath: "shadowPath")
animation.fromValue = previousPat
animation.toValue = shadowLayer.path
animation.autoreverses = false
animation.duration = 0.8
shadowLayer.add(animation, forKey: "pathAnimation")
}
layer.insertSublayer(shadowLayer, at: 0)
previousPat = shadowLayer.path!
}
}
Main ViewController code:
final class ViewController: UIViewController {
#IBOutlet weak var stack: UIStackView!
#IBOutlet weak var redContainerLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func animateAction(_ sender: Any) {
UIView.animate(withDuration: 0.8) {
self.redContainerLabel.alpha = self.redContainerLabel.alpha == 0 ? 1 : 0
self.stack.arrangedSubviews.forEach {
$0.isHidden.toggle()
}
}
}
}
View structure:
I was looking for an answer to exactly this problem and encountered this post here, which I believe is what you're looking for:
Animating CALayer shadow simultaneously as UITableviewCell height animates
The solution is a bit verbose (I'm surprised the use-case is so rare as to not prompt apple to fix(?) it) and a few years old at this point, but the most recent I could find. Ultimately I decided to work around it by fading out my shadows before resizing, but you might find it useful!
A couple of other, older posts I've found appear to corroborated the level of effort required for this effect (again, Apple's got a weird list of things it decides are your problem), so that first post is probably your best bet.
https://petosoft.wordpress.com/2012/10/24/shadowpath-not-animating-with-standard-uiview-animations-on-ipad/
Smoothly rotate and change size of UIView with shadow
UIView: How to animate CALayer frame with shadow?
If you've managed to find a more straightforward solution, by all means let me know; in the mean-time I hope this helps.

Bottom part of tab bar does not get properly colored

I want to custom color my tab bar element in my custom UITabController sub-class and it works fine when I'm doing it with:
tabBar.barTintColor = .blue (With any system or custom color)
But when I'm trying to add a gradient using my custom UIImage extension
extension UIImage {
static func gradientImageWithBounds(bounds: CGRect, colors: [CGColor]) -> UIImage {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colors
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
gradientLayer.masksToBounds = true
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
tabBar.barTintColor = UIColor(patternImage: UIImage.gradientImageWithBounds(bounds: tabBar.bounds, colors: [Colors.tabBarTopGradient, Colors.tabBarBottomGradient]))
I having trouble with gradient not getting properly applied to the bottom part of the tab bar around the phone's safe area. What am I missing here? It looks like that:
Please try setting the gradient tint in viewWillLayoutSubviews(). Hope that helps!
I think you create the gradient based on tab bar height, what you need is to add the additional height to the tab bar height
override func viewDidLoad() {
super.viewDidLoad()
var frame = tabBar.bounds
let safeAreaHeight = safeAreaInsets.bottom
frame.size.height = frame.size.height + safeAreaHeight
tabBar.barTintColor = UIColor(patternImage: UIImage.gradientImageWithBounds(bounds: frame, colors: [UIColor.red.cgColor, UIColor.blue.cgColor]))
}
public var safeAreaInsets: UIEdgeInsets {
guard let window: UIWindow = UIApplication.shared.windows.first else {
return .zero
}
if #available(iOS 11.0, *),
UIWindow.instancesRespond(to: #selector(getter: window.safeAreaInsets)) {
return window.safeAreaInsets
}
return .zero
}

ImageView Gradient behaving abnormally

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.

Change colors in a CAGradientLayer after inserting

I have a class function to create a CAGradient layer and insert it to a UIView which works fine.
However, I'd like to modify the gradient after the page is shown, and re-executing the same function does not work.
On the displayed page I use sliders to set new colors into two UIButton backgrounds, and call the function below. I'm expecting the gradient layer background to change when I change the slider but it doesn't. I've verified that the function is being called and the color values are valid.
How can I change the gradient as I move the sliders?
Here's the class function:
class func setGradientBG(_ firstColor: CGColor, secondColor: CGColor, vertical: Bool, thisView: UIView){
let gradientLayer = CAGradientLayer()
gradientLayer.frame = thisView.bounds
gradientLayer.colors = [firstColor, secondColor]
gradientLayer.locations = [0.0, 1.2]
if !vertical{
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
}
thisView.layer.insertSublayer(gradientLayer, at: 0)
}
And here's the function called from the sliders:
func doSlider(){
CommonFuncs.setGradientBG((self.TopColorBut.backgroundColor?.cgColor)!, secondColor: (self.BottomColorBut.backgroundColor?.cgColor)!, vertical: true, thisView: self.view)
}
You're adding another instance of CAGradientLayer every time you call setGradientBG. Do you really think that's a good idea?
It looks like your doSlider method is probably part of a custom UIViewController subclass. What you should do is store a reference to the gradient layer in the view controller, and update that existing layer. Example:
class ViewController: UIViewController {
private let gradientLayer = CAGradientLayer()
#IBOutlet var topColorButton: UIButton!
#IBOutlet var bottomColorButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
view.layer.insertSublayer(gradientLayer, at: 0)
self.updateGradientLayer()
}
#IBAction func doSlider() {
updateGradientLayer()
}
private func updateGradientLayer() {
gradientLayer.locations = [ 0.0, 1.2 ]
gradientLayer.startPoint = .zero
gradientLayer.endPoint = CGPoint(x: 0, y: 1)
gradientLayer.colors = [
(topColorButton.backgroundColor ?? .clear).cgColor,
(bottomColorButton.backgroundColor ?? .clear).cgColor ]
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
gradientLayer.frame = view.bounds
}
}
Sublayers are listed in back to front order. So insert the new sublayer at last. Or remove old ones.

Why is my table view shadow scrolling with my table view?

I added shadow to my table view but unfortunately when I scroll through the table view the shadow also moves with the table. The code for adding shadow is as follows:
func addShadow(to myView: UIView){
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: uiView.frame.width, height: uiView.frame.height * 1.1))
uiView.layer.shadowColor = UIColor.black.cgColor
uiView.layer.shadowOffset = CGSize(width: 0.2, height: 0)
uiView.layer.shadowOpacity = 0.5
uiView.layer.shadowRadius = 5.0
uiView.layer.masksToBounds = false
uiView.layer.shadowPath = shadowPath.cgPath
}
Can you please explain to me why is this happening and how to make the shadow stick to its designated location?
Thank you in advance.
If you are adding shadow on tableView, it will scroll along with tableview data. To prevent that you have to add UIView first. Add tableview on that view. Add shadow for the UIView you have taken. It will stick to designated location.
This is my solution in Swift 3 with an UIView and a CAGradientLayer inside.
override func viewWillAppear(_ animated: Bool) {
addShadow(myView: myTab)
}
func addShadow(myView: UIView){
let shadowView = UIView()
shadowView.center = CGPoint(x: myView.frame.minX,y:myView.frame.minY - 15)
shadowView.frame.size = CGSize(width: myView.frame.width, height: 15)
let gradient = CAGradientLayer()
gradient.frame.size = shadowView.frame.size
let stopColor = UIColor.clear.cgColor
let startColor = UIColor.black.withAlphaComponent(0.8).cgColor
gradient.colors = [stopColor,startColor]
gradient.locations = [0.0,1.0]
shadowView.layer.addSublayer(gradient)
view.addSubview(shadowView)
}

Resources