inner shadow effect UIView - ios

I want to add inner shadow to this UIlabel
Picture:
reference form Inner shadow effect on UIView layer?
let display = UILabel()
func setBackgroundLayer () {
let backgroundLayer = CAGradientLayer()
backgroundLayer.frame = view.bounds
backgroundLayer.colors = [UIColor(white:1,alpha:0.5).cgColor,UIColor(white:0.5,alpha:0.5).cgColor]
view.layer.insertSublayer(backgroundLayer, at: 0)
}
func setDisplay() {
let Xratio = view.bounds.width/8
display.frame = CGRect(x:Xratio,y:80,width:Xratio*6,height:Xratio*2)
display.backgroundColor = UIColor(red:204/255,green:203/255,blue:181/255,alpha:1)
view.addSubview(display)
}
func displayInnerShadow() {
let shadowLayer = CAShapeLayer()
shadowLayer.frame = view.bounds
let path = CGMutablePath()
path.addRect(display.frame.insetBy(dx: -20, dy: -20))
path.addRect(display.frame)
shadowLayer.fillRule = kCAFillRuleEvenOdd
shadowLayer.path = path
shadowLayer.shadowColor = UIColor(white:0,alpha:1).cgColor
shadowLayer.shadowOffset = CGSize(width:0,height:0)
shadowLayer.shadowOpacity = 1
shadowLayer.shadowRadius = 5
view.layer.insertSublayer(shadowLayer, at: 1)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setBackgroundLayer()
setDisplay()
//displayInnerShadow()
}
I got this result:
Why isn't it working and how to set the fill color?

try this
let display = UILabel()
func setBackgroundLayer () {
let backgroundLayer = CAGradientLayer()
backgroundLayer.frame = view.bounds
backgroundLayer.colors = [UIColor(white:1,alpha:0.5).cgColor,UIColor(white:0.5,alpha:0.5).cgColor]
view.layer.insertSublayer(backgroundLayer, at: 0)
}
func setDisplay() {
let Xratio = view.bounds.width/8
display.frame = CGRect(x:Xratio,y:80,width:Xratio*6,height:Xratio*2)
set layer background color instead of display.backgroundColor
display.layer.backgroundColor = UIColor(red:204/255,green:203/255,blue:181/255,alpha:1).cgColor
view.addSubview(display)
}
func displayInnerShadow() {
let innerShadowLayer = CALayer()
innerShadowLayer.frame = display.bounds
let path = UIBezierPath(rect: innerShadowLayer.bounds.insetBy(dx: -20, dy: -20))
let innerPart = UIBezierPath(rect: innerShadowLayer.bounds).reversing()
path.append(innerPart)
innerShadowLayer.shadowPath = path.cgPath
innerShadowLayer.masksToBounds = true
innerShadowLayer.shadowColor = UIColor.black.cgColor
innerShadowLayer.shadowOffset = CGSize.zero
innerShadowLayer.shadowOpacity = 1
innerShadowLayer.shadowRadius = 5
display.layer.addSublayer(innerShadowLayer)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setDisplay()
setBackgroundLayer()
displayInnerShadow()
}

Related

CALayer Presentation nil

I am trying to use a CABasicAnimation for the timing function with custom objects (not UIView).
I'm trying to implement #CIFilter's answer from here which is to use the CALayer's presentation layer that is animated to evaluate the timing function.
I'm doing it all in viewDidAppear, so a valid view exists, but no matter what I do, the Presentation layer is always nil.
Note that I have to add the animation to the view's layer and not the layer I've added to it for it to animate at all. And if I uncomment the lines commented out below I can see that the animation works (but only when animating the root layer). Regardless, the Presentation layer is nil.
I've looked at dozen's of tutorials and SO answers, and it seems this should just work, so I suppose I must be doing something stupid.
I am just trying to use the CoreAnimation timing functions. I have UICubicTimingParameters working, but seems like going the CA route offers much more functionality which would be nice.
import UIKit
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let newView = UIView(frame: view.frame)
view.addSubview(newView)
let evaluatorLayer = CALayer()
evaluatorLayer.frame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0)
evaluatorLayer.borderWidth = 8.0
evaluatorLayer.borderColor = UIColor.purple.cgColor
evaluatorLayer.timeOffset = 0.3
evaluatorLayer.isHidden = true
// evaluatorLayer.isHidden = false
newView.layer.addSublayer(evaluatorLayer)
let basicAnimation = CABasicAnimation(keyPath: "bounds.origin.x")
basicAnimation.duration = 1.0
basicAnimation.fromValue = 0.0
basicAnimation.toValue = 100.0
basicAnimation.fillMode = .forwards
basicAnimation.isRemovedOnCompletion = false
basicAnimation.speed = 0.0
// basicAnimation.speed = 0.1
newView.layer.add(basicAnimation, forKey: "evaluate")
if let presentationLayer = newView.layer.presentation() {
let evaluatedValue = presentationLayer.bounds.origin.x
print("evaluatedValue: \(evaluatedValue)")
}
else {
print(evaluatorLayer.presentation())
}
}
}
Not sure if your code is going to do what you expect, but...
I think the reason .presentation() is nil is because you haven't given UIKit an opportunity to apply the animation.
Try this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let newView = UIView(frame: view.frame)
view.addSubview(newView)
let evaluatorLayer = CALayer()
evaluatorLayer.frame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0)
evaluatorLayer.borderWidth = 8.0
evaluatorLayer.borderColor = UIColor.purple.cgColor
evaluatorLayer.timeOffset = 0.3
evaluatorLayer.isHidden = true
// evaluatorLayer.isHidden = false
newView.layer.addSublayer(evaluatorLayer)
let basicAnimation = CABasicAnimation(keyPath: "bounds.origin.x")
basicAnimation.duration = 1.0
basicAnimation.fromValue = 0.0
basicAnimation.toValue = 100.0
basicAnimation.fillMode = .forwards
basicAnimation.isRemovedOnCompletion = false
basicAnimation.speed = 0.0
// basicAnimation.speed = 0.1
newView.layer.add(basicAnimation, forKey: "evaluate")
DispatchQueue.main.async {
if let presentationLayer = newView.layer.presentation() {
let evaluatedValue = presentationLayer.bounds.origin.x
print("async evaluatedValue: \(evaluatedValue)")
}
else {
print("async", evaluatorLayer.presentation())
}
}
if let presentationLayer = newView.layer.presentation() {
let evaluatedValue = presentationLayer.bounds.origin.x
print("immediate evaluatedValue: \(evaluatedValue)")
}
else {
print("immediate", evaluatorLayer.presentation())
}
}
My debug output is:
immediate nil
async evaluatedValue: 0.0
Edit
I'm still not sure what your goal is, but give this a try...
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let newView = UIView(frame: view.frame)
view.addSubview(newView)
let evaluatorLayer = CALayer()
evaluatorLayer.frame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0)
evaluatorLayer.borderWidth = 8.0
evaluatorLayer.borderColor = UIColor.purple.cgColor
evaluatorLayer.isHidden = true
//evaluatorLayer.isHidden = false
newView.layer.addSublayer(evaluatorLayer)
let basicAnimation = CABasicAnimation(keyPath: "bounds.origin.x")
basicAnimation.duration = 1.0
basicAnimation.fromValue = 0.0
basicAnimation.toValue = 100.0
basicAnimation.fillMode = .forwards
basicAnimation.isRemovedOnCompletion = false
basicAnimation.speed = 0.0
//basicAnimation.speed = 1.0
// set timeOffset on the animation, not on the layer itself
basicAnimation.timeOffset = 0.3
// add animation to evaluatorLayer
evaluatorLayer.add(basicAnimation, forKey: "evaluate")
DispatchQueue.main.async {
// get presentation layer of evaluatorLayer
if let presentationLayer = evaluatorLayer.presentation() {
let evaluatedValue = presentationLayer.bounds.origin.x
print("async evaluatedValue: \(evaluatedValue)")
}
else {
print("async", evaluatorLayer.presentation())
}
}
}
In this example, we apply the .timeOffset on the animation, not on the layer. And, we add the animation to the evaluatorLayer, not to the newView.layer.
Output (for my quick test):
async evaluatedValue: 30.000001192092896

Animate UIView's Layer with constrains (Auto Layout Animations)

I am working on project where I need to animate height of view which consist of shadow, gradient and rounded corners (not all corners).
So I have used layerClass property of view.
Below is simple example demonstration.
Problem is that, when I change height of view by modifying its constraint, it was resulting in immediate animation of layer class, which is kind of awkward.
Below is my sample code
import UIKit
class CustomView: UIView{
var isAnimating: Bool = false
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
}
func setupView(){
self.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
guard let layer = self.layer as? CAShapeLayer else { return }
layer.fillColor = UIColor.green.cgColor
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
layer.shadowOpacity = 0.6
layer.shadowRadius = 5
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override class var layerClass: AnyClass {
return CAShapeLayer.self
}
override func layoutSubviews() {
super.layoutSubviews()
// While animating `myView` height, this method gets called
// So new bounds for layer will be calculated and set immediately
// This result in not proper animation
// check by making below condition always true
if !self.isAnimating{ //if true{
guard let layer = self.layer as? CAShapeLayer else { return }
layer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath
layer.shadowPath = layer.path
}
}
}
class TestViewController : UIViewController {
// height constraint for animating height
var heightConstarint: NSLayoutConstraint?
var heightToAnimate: CGFloat = 200
lazy var myView: CustomView = {
let view = CustomView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .clear
return view
}()
lazy var mySubview: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .yellow
return view
}()
lazy var button: UIButton = {
let button = UIButton(frame: .zero)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Animate", for: .normal)
button.setTitleColor(.black, for: .normal)
button.addTarget(self, action: #selector(self.animateView(_:)), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(self.myView)
self.myView.addSubview(self.mySubview)
self.view.addSubview(self.button)
self.myView.leadingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.leadingAnchor).isActive = true
self.myView.trailingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.trailingAnchor).isActive = true
self.myView.topAnchor.constraint(equalTo: self.view.layoutMarginsGuide.topAnchor, constant: 100).isActive = true
self.heightConstarint = self.myView.heightAnchor.constraint(equalToConstant: 100)
self.heightConstarint?.isActive = true
self.mySubview.leadingAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.leadingAnchor).isActive = true
self.mySubview.trailingAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.trailingAnchor).isActive = true
self.mySubview.topAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.topAnchor).isActive = true
self.mySubview.bottomAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.bottomAnchor).isActive = true
self.button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
self.button.bottomAnchor.constraint(equalTo: self.view.layoutMarginsGuide.bottomAnchor, constant: -20).isActive = true
}
#objc func animateView(_ sender: UIButton){
CATransaction.begin()
CATransaction.setAnimationDuration(5.0)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut))
UIView.animate(withDuration: 5.0, animations: {
self.myView.isAnimating = true
self.heightConstarint?.constant = self.heightToAnimate
// this will call `myView.layoutSubviews()`
// and layer's new bound will set automatically
// this causing layer to be directly jump to height 200, instead of smooth animation
self.view.layoutIfNeeded()
}) { (success) in
self.myView.isAnimating = false
}
let shadowPathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.shadowPath))
let pathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.path))
let toValue = UIBezierPath(
roundedRect:CGRect(x: 0, y: 0, width: self.myView.bounds.width, height: heightToAnimate),
cornerRadius: 10
).cgPath
shadowPathAnimation.fromValue = self.myView.layer.shadowPath
shadowPathAnimation.toValue = toValue
pathAnimation.fromValue = (self.myView.layer as! CAShapeLayer).path
pathAnimation.toValue = toValue
self.myView.layer.shadowPath = toValue
(self.myView.layer as! CAShapeLayer).path = toValue
self.myView.layer.add(shadowPathAnimation, forKey: #keyPath(CAShapeLayer.shadowPath))
self.myView.layer.add(pathAnimation, forKey: #keyPath(CAShapeLayer.path))
CATransaction.commit()
}
}
While animating view, it will call its layoutSubviews() method, which will result into recalculating bounds of shadow layer.
So I checked if view is currently animating, then do not recalculate bounds of shadow layer.
Is this approach right ? or there is any better way to do this ?
I know it's a tricky question. Actually, you don't need to care about layoutSubViews at all. The key here is when you set the shapeLayer. If it's setup well, i.e. after the constraints are all working, you don't need to care that during the animation.
//in CustomView, comment out the layoutSubViews() and add updateLayer()
func updateLayer(){
guard let layer = self.layer as? CAShapeLayer else { return }
layer.path = UIBezierPath(roundedRect: layer.bounds, cornerRadius: 10).cgPath
layer.shadowPath = layer.path
}
// override func layoutSubviews() {
// super.layoutSubviews()
//
// // While animating `myView` height, this method gets called
// // So new bounds for layer will be calculated and set immediately
// // This result in not proper animation
//
// // check by making below condition always true
//
// if !self.isAnimating{ //if true{
// guard let layer = self.layer as? CAShapeLayer else { return }
//
// layer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath
// layer.shadowPath = layer.path
// }
// }
in ViewController: add viewDidAppear() and remove other is animation block
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
myView.updateLayer()
}
#objc func animateView(_ sender: UIButton){
CATransaction.begin()
CATransaction.setAnimationDuration(5.0)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut))
UIView.animate(withDuration: 5.0, animations: {
self.heightConstarint?.constant = self.heightToAnimate
// this will call `myView.layoutSubviews()`
// and layer's new bound will set automatically
// this causing layer to be directly jump to height 200, instead of smooth animation
self.view.layoutIfNeeded()
}) { (success) in
self.myView.isAnimating = false
}
....
Then you are good to go. Have a wonderful day.
Below code also worked for me, As I want to use layout subviews without any flags.
UIView.animate(withDuration: 5.0, animations: {
let shadowPathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.shadowPath))
let pathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.path))
let toValue = UIBezierPath(
roundedRect:CGRect(x: 0, y: 0, width: self.myView.bounds.width, height: self.heightToAnimate),
cornerRadius: 10
).cgPath
shadowPathAnimation.fromValue = self.myView.layer.shadowPath
shadowPathAnimation.toValue = toValue
pathAnimation.fromValue = (self.myView.layer as! CAShapeLayer).path
pathAnimation.toValue = toValue
self.heightConstarint?.constant = self.heightToAnimate
self.myView.layoutIfNeeded()
self.myView.layer.shadowPath = toValue
(self.myView.layer as! CAShapeLayer).path = toValue
self.myView.layer.add(shadowPathAnimation, forKey: #keyPath(CAShapeLayer.shadowPath))
self.myView.layer.add(pathAnimation, forKey: #keyPath(CAShapeLayer.path))
CATransaction.commit()
})
And overriding layoutSubview as follows
override func layoutSubviews() {
super.layoutSubviews()
guard let layer = self.layer as? CAShapeLayer else { return }
layer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath
layer.shadowPath = layer.path
}

IOS Step menu using swift

I would like to create the stepper menu in IOS using swift, But I'm facing some issues. Here are the issues.
1) Portrait and landscape stepper menu is not propper.
2) How to set default step position with the method below method, It's working when button clicked. But I want to set when menu loads the first time.
self.stepView.setSelectedPosition(index: 2)
3) If it reached the position last, I would like to change the color for complete path parentPathRect.
4) Progress animation CABasicAnimation is not like the progress bar, I want to show the animation.
5) It should not remove the selected position color when changing the orientation.
As per my organization rules should not use third-party frameworks.
Can anyone help me with the solution? Or is there any alternative solution for this?
Here is my code:
import UIKit
class ViewController: UIViewController, StepMenuDelegate {
#IBOutlet weak var stepView: StepView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.stepView.delegate = self;
self.stepView.titles = ["1", "2", "3"]
self.stepView.lineWidth = 8
self.stepView.offSet = 8
self.stepView.setSelectedPosition(index: 2)
}
func didSelectItem(atIndex index: NSInteger) {
print(index)
}
}
protocol StepMenuDelegate {
func didSelectItem(atIndex index: NSInteger)
}
class StepView: UIView {
var delegate : StepMenuDelegate!
var titles: [String] = [] {
didSet(values) {
setup()
setupItems()
}
}
var lineWidth: CGFloat = 8 {
didSet(values) {
setup()
}
}
var offSet: CGFloat = 8 {
didSet(values) {
self.itemOffset = offSet * 4
setup()
}
}
private var selectedIndex : NSInteger!
private var itemOffset : CGFloat = 8 {
didSet (value) {
setup()
setupItems()
}
}
private var path : UIBezierPath!
var selectedLayer : CAShapeLayer!
private var parentPathRect : CGRect!
override func awakeFromNib() {
super.awakeFromNib()
}
override func layoutSubviews() {
self.setup()
setupItems()
}
func setup() {
self.removeAllButtonsAndLayes()
let layer = CAShapeLayer()
self.parentPathRect = CGRect(origin: CGPoint(x: offSet, y: self.bounds.midY - (self.lineWidth/2) ), size: CGSize(width: self.bounds.width - (offSet * 2), height: lineWidth))
path = UIBezierPath(roundedRect: self.parentPathRect, cornerRadius: 2)
layer.path = path.cgPath
layer.fillColor = UIColor.orange.cgColor
layer.lineCap = .butt
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowOffset = CGSize(width: 1, height: 2)
layer.shadowOpacity = 0.1
layer.shadowRadius = 2
self.layer.addSublayer(layer)
}
func setupItems() {
removeAllButtonsAndLayes()
let itemRect = CGRect(x: self.itemOffset, y: 0, width: 34, height: 34)
let totalWidth = self.bounds.width
let itemWidth = totalWidth / CGFloat(self.titles.count);
for i in 0..<self.titles.count {
let button = UIButton()
var xPos: CGFloat = itemOffset
self.addSubview(button)
xPos += (CGFloat(i) * itemWidth);
xPos += itemOffset/3
button.translatesAutoresizingMaskIntoConstraints = false
button.leftAnchor.constraint(equalTo: self.leftAnchor, constant: xPos).isActive = true
button.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0).isActive = true
button.heightAnchor.constraint(equalToConstant: itemRect.height).isActive = true
button.widthAnchor.constraint(equalToConstant: itemRect.width).isActive = true
button.backgroundColor = UIColor.red
button.layer.zPosition = 1
button.layer.cornerRadius = itemRect.height/2
let name : String = self.titles[i]
button.tag = i
button.setTitle(name, for: .normal)
button.addTarget(self, action: #selector(selectedItemEvent(sender:)), for: .touchUpInside)
if self.selectedIndex != nil {
if button.tag == self.selectedIndex {
selectedItemEvent(sender: button)
}
}
}
}
#objc func selectedItemEvent(sender:UIButton) {
if self.selectedLayer != nil {
selectedLayer.removeFromSuperlayer()
}
self.delegate.didSelectItem(atIndex: sender.tag)
let fromRect = self.parentPathRect.origin
self.selectedLayer = CAShapeLayer()
let rect = CGRect(origin: fromRect, size: CGSize(width:sender.frame.origin.x - 4, height: 8))
let path = UIBezierPath(roundedRect: rect, cornerRadius: 4)
self.selectedLayer.path = path.cgPath
self.selectedLayer.lineCap = .round
self.selectedLayer.fillColor = UIColor.orange.cgColor
let animation = CABasicAnimation(keyPath: "fillColor")
animation.toValue = UIColor.blue.cgColor
animation.duration = 0.2
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
self.selectedLayer.add(animation, forKey: "fillColor")
self.layer.addSublayer(self.selectedLayer)
}
func removeAllButtonsAndLayes() {
for button in self.subviews {
if button is UIButton {
button.removeFromSuperview()
}
}
}
func setSelectedPosition(index:NSInteger) {
self.selectedIndex = index
}
}
Here I found One way to achieve the solution.!!
https://gist.github.com/DamodarGit/7f0f484708f60c996772ae28e5e1c615
Welcome to suggestions or code changes.!!

class is not working correctly after swift 3 conversion [duplicate]

This question already has answers here:
UIColor not working with RGBA values
(6 answers)
Closed 6 years ago.
my class (UIView) is not working after Xcode 8.1's swift 3 conversion i have no idea whats wrong here , this class is a progress view which is looking fine after conversion but my progress is not visible here's my class after the conversion :
class CircularLoaderView: UIView, CAAnimationDelegate {
let circlePathLayer = CAShapeLayer()
let circleRadius: CGFloat = 60.0
let innerCirclePathLayer = CAShapeLayer()
let innerCircleRadius: CGFloat = 60.0
override init(frame: CGRect) {
super.init(frame: frame)
configure()
innerConfigure()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
configure()
innerConfigure()
}
func configure() {
circlePathLayer.frame = bounds
circlePathLayer.lineWidth = 10
circlePathLayer.fillColor = UIColor.clear.cgColor
circlePathLayer.strokeColor = UIColor.darkGray.cgColor
layer.addSublayer(circlePathLayer)
backgroundColor = UIColor.clear
progress = 0
}
func innerConfigure() {
innerCirclePathLayer.frame = bounds
innerCirclePathLayer.lineWidth = 10
innerCirclePathLayer.fillColor = UIColor.clear.cgColor
innerCirclePathLayer.strokeColor = UIColor(red: 100, green: 60, blue: 70, alpha: 0.2).cgColor
layer.addSublayer(innerCirclePathLayer)
backgroundColor = UIColor.clear
}
func innerCircleFrame() -> CGRect {
var circleFrame = CGRect(x: 0, y: 0, width: 2*innerCircleRadius, height: 2*innerCircleRadius)
circleFrame.origin.x = innerCirclePathLayer.bounds.midX - circleFrame.midX
circleFrame.origin.y = innerCirclePathLayer.bounds.midY - circleFrame.midY
return circleFrame
}
func innerCirclePath() -> UIBezierPath {
return UIBezierPath(ovalIn: innerCircleFrame())
}
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: 0, y: 0, width: 2*circleRadius, height: 2*circleRadius)
circleFrame.origin.x = circlePathLayer.bounds.midX - circleFrame.midX
circleFrame.origin.y = circlePathLayer.bounds.midY - circleFrame.midY
return circleFrame
}
func circlePath() -> UIBezierPath {
return UIBezierPath(ovalIn: circleFrame())
}
override func layoutSubviews() {
super.layoutSubviews()
circlePathLayer.frame = bounds
circlePathLayer.path = circlePath().cgPath
innerCirclePathLayer.frame = bounds
innerCirclePathLayer.path = innerCirclePath().cgPath
}
var progress: CGFloat {
get {
return circlePathLayer.strokeEnd
}
set {
if (newValue > 1) {
circlePathLayer.strokeEnd = 1
} else if (newValue < 0) {
circlePathLayer.strokeEnd = 0
} else {
circlePathLayer.strokeEnd = newValue
}
}
}
func reveal() {
// 1
backgroundColor = UIColor.clear
progress = 1
// 2
circlePathLayer.removeAnimation(forKey: "strokeEnd")
// 3
circlePathLayer.removeFromSuperlayer()
superview?.layer.mask = circlePathLayer
// 1
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let finalRadius = sqrt((center.x*center.x) + (center.y*center.y))
let radiusInset = finalRadius - circleRadius
let outerRect = circleFrame().insetBy(dx: -radiusInset, dy: -radiusInset)
let toPath = UIBezierPath(ovalIn: outerRect).cgPath
// 2
let fromPath = circlePathLayer.path
let fromLineWidth = circlePathLayer.lineWidth
// 3
CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
circlePathLayer.lineWidth = 2*finalRadius
circlePathLayer.path = toPath
CATransaction.commit()
// 4
let lineWidthAnimation = CABasicAnimation(keyPath: "lineWidth")
lineWidthAnimation.fromValue = fromLineWidth
lineWidthAnimation.toValue = 2*finalRadius
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.fromValue = fromPath
pathAnimation.toValue = toPath
// 5
let groupAnimation = CAAnimationGroup()
groupAnimation.duration = 1
groupAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
groupAnimation.animations = [pathAnimation, lineWidthAnimation]
groupAnimation.delegate = self
circlePathLayer.add(groupAnimation, forKey: "strokeWidth")
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
superview?.layer.mask = nil
}
}
this is how i'm setting the progress :
cell.loaderView?.progress = CGFloat(receivedSize)/CGFloat(expectedSize)
still its not showing any progress , anyone have any clue whats wrong here then let me know
You haven't specify what exactly not working, but when I test your code it showing progress the only thing is not working was it is not showing me the innerCirclePathLayer because you have not divide your RGB color with 255 because init(red:green:blue:alpha:) accept values between 0.0 to 1.0 and values above 1.0 are interpreted as 1.0. So try once dividing RGB to 255.
innerCirclePathLayer.strokeColor = UIColor(red: 100/255.0, green: 60/255.0, blue: 70/255.0, alpha: 0.2).cgColor

Draw hole on UIBlurEffect

Xcode 8.0 - Swift 2.3
I have an internal extension to create blur layer that works great:
internal extension UIView {
/**
Add and display on current view a blur effect.
*/
internal func addBlurEffect(style style: UIBlurEffectStyle = .ExtraLight, atPosition position: Int = -1) -> UIView {
// Blur Effect
let blurEffectView = self.createBlurEffect(style: style)
if position >= 0 {
self.insertSubview(blurEffectView, atIndex: position)
} else {
self.addSubview(blurEffectView)
}
return blurEffectView
}
internal func createBlurEffect(style style: UIBlurEffectStyle = .ExtraLight) -> UIView {
let blurEffect = UIBlurEffect(style: style)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = self.bounds
blurEffectView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
return blurEffectView
}
}
Question is: how can I add a shaped hole in blur overlay?
I have made many attempts:
let p = UIBezierPath(roundedRect: CGRectMake(0.0, 0.0, self.viewBlur!.frame.width, self.viewBlur!.frame.height), cornerRadius: 0.0)
p.usesEvenOddFillRule = true
let f = CAShapeLayer()
f.fillColor = UIColor.redColor().CGColor
f.opacity = 0.5
f.fillRule = kCAFillRuleEvenOdd
p.appendPath(self.holePath)
f.path = p.CGPath
self.viewBlur!.layer.addSublayer(f)
but result is:
I can't understand why hole is ok on UIVisualEffectView but not in _UIVisualEffectBackdropView
UPDATE
I've tryied #Arun solution (with UIBlurEffectStyle.Dark), but result is not the same:
UPDATE 2
With #Dim_ov's solution I have:
In order to make this work I need to hide _UIVisualEffectBackdropViewin this way:
for v in effect.subviews {
if let filterView = NSClassFromString("_UIVisualEffectBackdropView") {
if v.isKindOfClass(filterView) {
v.hidden = true
}
}
}
In iOS 10 you have to use mask property of UIVisualEffectView instead of CALayer's mask.
I saw this covered in the release notes for some early betas of iOS 10 or Xcode 8, but I can not find those notes now :). I'll update my answer with a proper link as soon as I find it.
So here is the code that works in iOS 10/Xcode 8:
class ViewController: UIViewController {
#IBOutlet var blurView: UIVisualEffectView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateBlurViewHole()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updateBlurViewHole()
}
func updateBlurViewHole() {
let maskView = UIView(frame: blurView.bounds)
maskView.clipsToBounds = true;
maskView.backgroundColor = UIColor.clear
let outerbezierPath = UIBezierPath.init(roundedRect: blurView.bounds, cornerRadius: 0)
let rect = CGRect(x: 150, y: 150, width: 100, height: 100)
let innerCirclepath = UIBezierPath.init(roundedRect:rect, cornerRadius:rect.height * 0.5)
outerbezierPath.append(innerCirclepath)
outerbezierPath.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = UIColor.green.cgColor // any opaque color would work
fillLayer.path = outerbezierPath.cgPath
maskView.layer.addSublayer(fillLayer)
blurView.mask = maskView;
}
}
Swift 2.3 version:
class ViewController: UIViewController {
#IBOutlet var blurView: UIVisualEffectView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
updateBlurViewHole()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updateBlurViewHole()
}
func updateBlurViewHole() {
let maskView = UIView(frame: blurView.bounds)
maskView.clipsToBounds = true;
maskView.backgroundColor = UIColor.clearColor()
let outerbezierPath = UIBezierPath.init(roundedRect: blurView.bounds, cornerRadius: 0)
let rect = CGRect(x: 150, y: 150, width: 100, height: 100)
let innerCirclepath = UIBezierPath.init(roundedRect:rect, cornerRadius:rect.height * 0.5)
outerbezierPath.appendPath(innerCirclepath)
outerbezierPath.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = UIColor.greenColor().CGColor
fillLayer.path = outerbezierPath.CGPath
maskView.layer.addSublayer(fillLayer)
blurView.maskView = maskView
}
}
UPDATE
Well, it was Apple Developer forums discussion and not iOS release notes. But there are answers from Apple's representatives so I think, this information may be considered "official".
A link to the discussion: https://forums.developer.apple.com/thread/50854#157782
Masking the layer of a visual effect view is not guaranteed to produce
the correct results – in some cases on iOS 9 it would produce an
effect that looked correct, but potentially sourced the wrong content.
Visual effect view will no longer source the incorrect content, but
the only supported way to mask the view is to either use cornerRadius
directly on the visual effect view’s layer (which should produce the
same result as you are attempting here) or to use the visual effect
view’s maskView property.
Please check my code here
internal extension UIView {
/**
Add and display on current view a blur effect.
*/
internal func addBlurEffect(style style: UIBlurEffectStyle = .ExtraLight, atPosition position: Int = -1) -> UIView {
// Blur Effect
let blurEffectView = self.createBlurEffect(style: style)
if position >= 0 {
self.insertSubview(blurEffectView, atIndex: position)
} else {
self.addSubview(blurEffectView)
}
return blurEffectView
}
internal func createBlurEffect(style style: UIBlurEffectStyle = .ExtraLight) -> UIView {
let blurEffect = UIBlurEffect(style: style)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = self.bounds
blurEffectView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
return blurEffectView
}
}
class ViewController: UIViewController {
#IBOutlet weak var blurView: UIImageView!
var blurEffectView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
blurEffectView = blurView.addBlurEffect(style: UIBlurEffectStyle.Light, atPosition: 0)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let outerbezierPath = UIBezierPath.init(roundedRect: self.blurEffectView.frame, cornerRadius: 0)
let rect = CGRectMake(150, 150, 100, 100)
let innerCirclepath = UIBezierPath.init(roundedRect:rect, cornerRadius:rect.height * 0.5)
outerbezierPath.appendPath(innerCirclepath)
outerbezierPath.usesEvenOddFillRule = false
let fillLayer = CAShapeLayer()
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = UIColor.blackColor().CGColor
fillLayer.path = outerbezierPath.CGPath
self.blurEffectView.layer.mask = fillLayer
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
With UIBlurEffectStyle.Light
With UIBlurEffectStyle.Dark
With UIBlurEffectStyle.ExtraLight
I've had the same issue in my code. After I applied the mask on the UIView containing UIVisualEffectView, the blur disappeared. However, I could turn it back by overriding the draw method on the parent:
override func draw(_ rect: CGRect) {
super.draw(rect)
}
I don't know why it works exactly, but hope it will help someone. The code I used to cut the hole out:
private func makeViewFinderMask() -> CAShapeLayer {
let path = UIBezierPath(rect: bounds)
let croppedPath = UIBezierPath(roundedRect: viewFinderBounds(), cornerRadius: viewFinderRadius)
path.append(croppedPath)
path.usesEvenOddFillRule = true
let mask = CAShapeLayer()
mask.path = path.cgPath
mask.fillRule = kCAFillRuleEvenOdd
return mask
}

Resources