How to set up UIBezierPath border for view in ios? - 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.

Related

How can I add Cell index path as Index next to the Collectionview cell?

I have collection view (If the process is easier on a table view I can change it to that).
I need to display the index path of the Cell just before displaying the collection view cell.
I have been trying to figure out how to work on this, here are the things I worked out (Nothing has been a good solution).
Firstly, I tried using a Collection view cell with an added Label (For numbering) and UIView (For showing the content next to it). But the shadow code is not working for the UIView on the collection view cell.
#objc extension CALayer {
func applySketchShadow(
color: UIColor = .black,
alpha: Float = 0.5,
x: CGFloat = 0,
y: CGFloat = 2,
blur: CGFloat = 4,
spread: CGFloat = 0)
{
shadowColor = color.cgColor
shadowOpacity = alpha
shadowOffset = CGSize(width: x, height: y)
shadowRadius = blur / 2.0
if spread == 0 {
shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}
#objc extension UIView{
func applyShadowToView(){
self.layer.borderWidth = 1.0
self.layer.borderColor = UIColor.clear.cgColor
self.layer.masksToBounds = true
self.layer.masksToBounds = false
self.layer.applySketchShadow(color: UIColor.black, alpha: 0.09, x: 3, y: 2, blur: 50, spread: 4)
}}
For some weird reasons, my shadow view is not showing up. I tried various codes from online just to be sure, nothing works out.
Other things I thought off are having a header view to show the number of the Collection view, but header view needs to be as wide as Frame so this is not an option and
the other thing is having 2 collection view cells, even cells showing the Number and odd cells showing content.
But the problem with this is I want to add rearranging functionality later on and this method will not work when I want to do it.

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

Adding CAShapeLayer to UIButton but it appears under the border line

I want to add a red dot to the border of a UIButton. My current code for adding a dot is like so:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
override init(frame: CGRect) {
super.init(frame: frame)
setUp()
}
override func setUp() {
super.setUp()
layer.borderWidth = borderWidth
layer.borderColor = normalBorderColor.cgColor
let redDotLayer = CAShapeLayer()
redDotLayer.path = CGPath(ellipseIn: CGRect(x: 30, y: -3.5, width: 8, height: 8), transform: nil)
redDotLayer.fillColor = UIColor.red.cgColor
layer.addSublayer(redDotLayer)
}
However when I add the red dot it appears under the border line. I need it to be on top of the border line.
What am I doing wrong here?
Nothing wrong with your approach.
For example just consider adding subview to parent UIView, subview always stays inside the parent it can't be added above the parent.
Same scenario applies to CALayer . You can't addSublayer above parent layer.
As an exception,
Unlike views, a superlayer does not automatically clip the contents of sublayers that lie outside its bounds rectangle. Instead, the superlayer allows its sublayers to be displayed in their entirety by default. However, you can reenable clipping by setting the masksToBounds property of the layer to YES.
as per apple documentation sublayer can go beyond parent visible region, but not above the parent.
Even below methods won't help us.
- insertSublayer:atIndex:
- insertSublayer:above:
- insertSublayer:below:
addSublayer:
Appends the layer to the layer’s list of sublayers.
Solution
Draw another CAShapeLayer around the button.
CALayers may just draw the border around itself last. I think the easiest solution for you is just be to draw the border in a separate layer so that you can control the ordering.
override func setUp() {
super.setUp()
let borderLayer = CALayer()
borderLayer.borderWidth = borderWidth
borderLayer.borderColor = normalBorderColor.cgColor
layer.addSublayer(borderLayer)
let redDotLayer = CAShapeLayer()
redDotLayer.path = CGPath(ellipseIn: CGRect(x: 30, y: -3.5, width: 8, height: 8), transform: nil)
redDotLayer.fillColor = UIColor.red.cgColor
layer.addSublayer(redDotLayer)
}
In my code, I was unable to use another layer as a border so I've used another UIView as a border.
An important part is to set the border UIView isUserInteractionEnabled to false so the responder chain would pass it to the UIButton:
let borderView = UIView(frame: .zero)
borderView.backgroundColor = .clear
borderView.layer.borderWidth = 0.5
borderView.layer.borderColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.18).cgColor
borderView.layer.cornerRadius = 19
// Dont't forget to set it to false or else the button won't work
borderView.isUserInteractionEnabled = false

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.

Gradient turns to black

I am using gradient in subview of UIView, after coming back to app by switching from some apps, the gradient becomes black. Don't know why, If anybody out there knows anything about this, help me out here.
Also i didn't subclass it programatically , directly put that in the UIView's class place in Storyboard itself (CustomGradientBlueView). Is that can be an issue.?
This is the code which am using for gradient ->
import UIKit
class CustomGradientBlueView: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
// colour declarations
let gradientColor1 = UIColor(red: 0.361, green: 0.145, blue: 0.553, alpha: 1.000)
let gradientColor2 = UIColor(red: 0.263, green: 0.537, blue: 0.635, alpha: 1.000)
// gradient decalarations
let gradient = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [gradientColor1.CGColor,gradientColor2.CGColor], [0,1])
//rectangle drawing
var screen = UIScreen.mainScreen().bounds.size
let rectanglePath = UIBezierPath(rect: CGRectMake(0,0, screen.width, screen.height))
CGContextSaveGState(context)
rectanglePath.addClip()
CGContextDrawLinearGradient(context, gradient, CGPointMake(0,0 ), CGPointMake(screen.width, screen.height), 0)
}
}
The problem is likely in these lines:
var screen = UIScreen.mainScreen().bounds.size
let rectanglePath = UIBezierPath(rect: CGRectMake(0,0, screen.width, screen.height))
CGContextSaveGState(context)
rectanglePath.addClip()
You are calling CGContextSaveGState without calling CGContextRestoreGState to balance it. That is very dangerous. If the context is maintained, which is perfectly possible, then you are saying addClip again with your previous clipping path already still in place - because you did not remove it with a balancing CGContextRestoreGState after drawing. Because of the nature of clipping paths, this causes you to end up with your whole view clipping, and your drawing is completely suppressed — hence the black view.
(It is not at all clear what you think you are doing with this clipping path in any case. There is no need to clip to the screen bounds, and accessing the screen bounds inside a view's drawRect: is a very odd thing to do. You might be happier just deleting those four lines altogether.)

Resources