I am using a badge library to help with adding large amounts of badges to UICollectionViewCells however for whatever reason, the badge itself stays behind the view.
However, once I go into the view debugger, this is what I see.
This is the code for my collection cell. I've tried different values in relation to maskToBounds and clipToBounds to no avail. Along with self and contentView.
import UIKit
import BadgeControl
class cellView: UICollectionViewCell {
private var upperLeftBadge: BadgeController!
override init(frame: CGRect) {
super.init(frame: frame)
roundCorners(corners: .allCorners, radius: 10)
backgroundColor = .init(hexString: "#37495b")
clipsToBounds = false
// contentView.isOpaque = true
//isOpaque = true
layer.masksToBounds = false
upperLeftBadge = BadgeController(for: self, in: .upperLeftCorner, badgeBackgroundColor: #colorLiteral(red: 0.9674351811, green: 0.2441418469, blue: 0.4023343325, alpha: 1) , badgeTextColor: UIColor.white, borderWidth: 0, badgeHeight: 20)
upperLeftBadge.addOrReplaceCurrent(with: "1", animated: true)
upperLeftBadge.animateOnlyWhenBadgeIsNotYetPresent = true
upperLeftBadge.animation = BadgeAnimations.leftRight
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I was using this view extension on the cells view to round corners. Removing the reference to this, and rounding the corners via layer.cornerRadius = 10 resolved my problem. Not too sure why this would hide overlapping views.
func roundCorners(corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
Have you tried this in your UICollectionViewCell class?
self.bringSubviewToFront(view: upperLeftBadge.view)
for notes self is your UICollectionViewCell I mean
Related
I tried to build a multiple section with a same UICollectionReusableView as a decorationItems. But when the view is reused during the scrolling, the gradientLayer inside the ReusableView didn't updated to meet the new section size. I have no ideas what the problem. How can I reset the gradientLayer frame when the UICollectionReusableView reused.
class RoundedBackgroundView: UICollectionReusableView {
private var insetView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 6
view.clipsToBounds = true
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
let gradientLayer = CAGradientLayer()
// Set the size of the layer to be equal to size of the display.
gradientLayer.frame = self.bounds
// Set an array of Core Graphics colors (.cgColor) to create the gradient.
gradientLayer.colors = [UIColor(red: 246, green: 186, blue: 58).cgColor, UIColor(red: 233, green: 115, blue: 58).cgColor]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
// Rasterize this static layer to improve app performance.
gradientLayer.shouldRasterize = true
insetView.layer.addSublayer(gradientLayer)
addSubview(insetView)
NSLayoutConstraint.activate([
insetView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0),
insetView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
insetView.topAnchor.constraint(equalTo: topAnchor),
insetView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
For reusable views I would always recommend to make a separated view, do all the layout in that view and add this to the cell/reusableView.
Anyways, the problem is probably, that the bounds aren't set at the init-method.
A way to solve it would be adding this function to your reusable view.
override func layoutSubviews() {
super.layoutSubviews()
if insetView.bounds.height > 0 {
gradientLayer.frame = insetView.bounds
}
}
I'm been trying to create a shadow for my UIView. I looked around and found an extension for the CALayer class from this post. https://stackoverflow.com/a/48489506/9188318
So far its been working well for me until I try to put in a non 0 number for the spread.
With the spread being 0. This is the result
And here is the result using a spread of 1
And it gets even worse with a spread of 5
The problem that I'm having is that it doesn't have rounded corners and I have no idea how to fix this. Heres my code for the UIView that uses this view. The extension that is used to make the shadow is in the post above.
UIView code
class FileCalculateOperatorSelectionButton : UIView {
private var isActivated : Bool = false
//Background colors
private var unSelectedBackgroundColor : UIColor = UIColor(red: 178/255, green: 90/255, blue: 253/255, alpha: 1.0)
private var selectedBackgroundColor : UIColor = UIColor.white
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.commonInit()
}
private func commonInit() {
self.layer.cornerRadius = self.frame.height/2
self.backgroundColor = self.unSelectedBackgroundColor
let shadowColor = UIColor(red: 0xC8, green: 0xC6, blue: 0xC6)
//Change the spread argument here
self.layer.applySketchShadow(color: .black, alpha: 0.5, x: 0, y: 0, blur: 5, spread: 0)
}
}
Try setting the layer's masksToBounds property to true.
self.layer.masksToBounds = true
Change shadowPath in applySketchShadow function in CALayer extension like below;
// shadowPath = UIBezierPath(rect: rect).cgPath
shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
when spread: 1
I am trying to create a custom UIButton that i can use throughout my application. I would like to apply a gradient as its background however the gradient is not showing up using this very simple Code.
It would be very helpful if someone could point out my mistake in the code below.
class GradientBtn: UIButton {
let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
themeConfig()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeConfig()
}
private func themeConfig() {
//shadow
layer.shadowOffset = CGSize.zero
layer.shadowColor = UIColor.gray.cgColor
layer.shadowOpacity = 1.0
//titletext
setTitleColor(Theme.colorWhite, for: .normal)
titleLabel?.font = UIFont(name: Theme.fontAvenir, size: 18)
//rounded corners
layer.cornerRadius = frame.size.height / 2
//gradient
gradientLayer.locations = [0.0, 1.0]
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.frame = bounds
layer.insertSublayer(gradientLayer, at: 0)
}
}
Replace
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue]
with
gradientLayer.colors = [Theme.colorlightBlue.cgColor, Theme.colorMidBlue.cgColor]
And it's always good to add
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = self.bounds
}
Sh_Khan is right about the CGColor references, but a few additional refinements:
Rather than adding the gradient as a sublayer, make it the layerClass of the UIButton. This makes animations much better (e.g. if the button changes size on device rotation).
When you do this, remove all attempts to set the frame of the gradientLayer.
In layoutSubviews, you’ll no longer need to update the gradientLayer.frame, so remove that entirely, but you do want to put your corner rounding there.
It’s extremely unlikely to ever matter, but when you set layer.cornerRadius (or any property of the view, its layer, or that layer's sublayers), you should never refer to self.frame. The frame of the view is the CGRect in the superview’s coordinate system. Only refer to the view’s bounds within the subclass; never its frame.
Thus:
class GradientBtn: UIButton {
override class var layerClass: AnyClass { return CAGradientLayer.self }
private var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
override init(frame: CGRect = .zero) {
super.init(frame: frame)
themeConfig()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeConfig()
}
private func themeConfig() {
//shadow
layer.shadowOffset = .zero
layer.shadowColor = UIColor.gray.cgColor
layer.shadowOpacity = 1
//titletext
setTitleColor(Theme.colorWhite, for: .normal)
titleLabel?.font = UIFont(name: Theme.fontAvenir, size: 18)
//gradient
gradientLayer.locations = [0, 1]
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue].map { $0.cgColor }
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = min(bounds.height, bounds.width) / 2
}
}
Below is the code for a custom Card View. The problem is, when I add the subviews to this in Interface builder it doesn't apply the corner radius to the subview. For the most part, I can get away with this by making subviews have a clear background color but I'm struggling with UIImageView. When I add that to a card it ends up with pointy corners and I've not been able to fix it.
Various solutions on here have suggested adding a second layer to display the shadow. I've attempted this but it still doesn't work as intended. What I'm trying to achieve is a view with rounded corners, drop shadow and adding any subviews (such as UIImageView) should also maintain the corner radius and not pointing out.
I've tried various settings with layer.masksToBounds and self.clipsToBounds and I always seem to get subviews with a corner radius but no shadow or the shadow visible and views not clipping.
#IBDesignable class CardView: UIView {
#IBInspectable dynamic var cornerRadius: CGFloat = 6
#IBInspectable dynamic var shadowOffsetWidth: Int = 2
#IBInspectable dynamic var shadowOffsetHeight: Int = 2
#IBInspectable dynamic var shadowColor: UIColor? = UIColor(netHex: 0x333333)
#IBInspectable dynamic var shadowOpacity: Float = 0.5
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
commonInit()
}
override func prepareForInterfaceBuilder() {
commonInit()
}
func commonInit() {
layer.cornerRadius = cornerRadius
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
layer.masksToBounds = false
layer.shadowColor = shadowColor?.cgColor
layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight)
layer.shadowOpacity = shadowOpacity
layer.shadowPath = shadowPath.cgPath
// This was how I tried to add a seperate shadow layer
// let shadowView = UIView(frame: self.frame)
// shadowView.layer.shadowColor = shadowColor?.cgColor
// shadowView.layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight)
// shadowView.layer.shadowOpacity = shadowOpacity
// shadowView.layer.shadowPath = shadowPath.cgPath
// shadowView.layer.masksToBounds = false
//
// self.addSubview(shadowView)
}
}
The way you were trying to implement a second view to handle shadows is almost correct, you just didn't keep the right order.
Your CardView class already handles displaying a shadow. Leave that view as it is and instead add a UIView called "ContentView" as a subview. That content view has the same frame and corner radius as your CardView.
On the "ContentView", you don't need to do any work with shadows. Instead, set its layer's masksToBounds property to true. Now add all the content you want to display in your Card to the "ContentView" and it should clip correctly.
func commonInit() {
layer.cornerRadius = cornerRadius
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
layer.masksToBounds = false
layer.shadowColor = shadowColor?.cgColor
layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight)
layer.shadowOpacity = shadowOpacity
layer.shadowPath = shadowPath.cgPath
let contentView = UIView()
contentView.frame = self.frame
contentView.layer.cornerRadius = cornerRadius
contentView.layer.masksToBounds = true
// any content you add should now be added to the contentView:
// contentView.addSubview(aView)
}
furthermore, you can specific corners.
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner]
I have the following parts:
- My main view is a UIViewController with a UICollectionView
- The cell for the UICollectionView
- A subclass of the UIView to build a CAShapeLayer with an CABasicAnimation
In my main view I have a UICollectionView which renders a bunch of cells with labels etc. It also is showing a progress graph.
In my subclass ProgressCirclePath() I am drawing a CAShapeLayer which is acting as the progress graph rendered in each cell of my UICollectionView.
I have been able to pass data to each cell, e.g. the labels as well as the CAShapeLayer strokeEnd values.
Everything is fine until I try to add a CABasicAnimation to my path. In this case I am not able to set the value for the animations toValue. Testing using the print console reveals that the value is available in my UICollectionView but not in the animation block in my subClass (which is where it simply returns nil). I have tried simply setting the toValue as well as creating a variable within my ProgressCirlePath and setting it from the UICollectionView. Neither worked.
I'd appreciate any hints on why this is happening and how to solve this. Thanks!!
Within my UICollectionView:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = decksCollectionView.dequeueReusableCell(withReuseIdentifier: "DeckCell", for: indexPath) as! DeckCell
cell.creatorLabel.text = deckCellCreator[indexPath.item]
cell.titleLabel.text = deckCellTitle[indexPath.item]
cell.progressLabel.text = "\(deckCellCompletionPercentage[indexPath.item])%"
cell.progressGraphView.animation.toValue = CGFloat(deckCellCompletionPercentage[indexPath.item])/100
return cell
}
The setup within my cell class:
let progressGraphView: ProgressCirclePath = {
let circlePath = ProgressCirclePath(frame: CGRect(x:0, y:0, width: 86, height: 86))
circlePath.progressLayer.position = circlePath.center
circlePath.progressBackgroundLayer.position = circlePath.center
circlePath.translatesAutoresizingMaskIntoConstraints = false
return circlePath
}()
And here my ProgressCirclePath()
class ProgressCirclePath: UIView {
let progressLayer = CAShapeLayer()
let progressBackgroundLayer = CAShapeLayer()
let animation = CABasicAnimation(keyPath: "strokeEnd")
// var percentageValue = CGFloat()
override init(frame: CGRect) {
super.init(frame: frame)
layer.addSublayer(progressBackgroundLayer)
layer.addSublayer(progressLayer)
let circularPath = UIBezierPath(arcCenter: .zero, radius: 43, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
progressBackgroundLayer.path = circularPath.cgPath
progressBackgroundLayer.lineWidth = 10
progressBackgroundLayer.strokeStart = 0
progressBackgroundLayer.strokeEnd = 1
progressBackgroundLayer.strokeColor = UIColor(red: 221/255.0, green: 240/255.0, blue: 226/255.0, alpha: 1.0).cgColor
progressBackgroundLayer.fillColor = UIColor.clear.cgColor
progressBackgroundLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)
progressLayer.path = circularPath.cgPath
progressLayer.lineWidth = 10
progressLayer.lineCap = kCALineCapRound
progressLayer.strokeColor = UIColor(red: 72/255.0, green: 172/255.0, blue: 104/255.0, alpha: 1.0).cgColor
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)
animation.duration = 1
animation.fromValue = 0
// animation.toValue = percentageValue
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
progressLayer.add(animation, forKey: "animateGraph")
print("animation.toValue \(animation.toValue)")
}
required init?(coder aDecoder: NSCoder) {
fatalError("has not been implemented")
}
}
You're handling the injection of data and animation too early in the lifecycle of the custom view. Instead of handling them in the object's initializer, move them to a later and more appropriate method, such as layoutSubviews:
override open func layoutSubviews() {
super.layoutSubviews()
// handle post-init stuff here, like animations and passing in data
// when it doesn't get passed in on init
}