Adding Blur effect to view - ios

I have a view class which is used for showing a spinner with a blurred background view. I add this to another view during runtime and everything works fine. When I add this view to another ViewControllers view somehow the effect is not visible.The view does get added, I check it by setting the background colour to green and it is there.But the effect itself is not visible.I add the view in this manner
Ex:
self.view.addSubview(self.loadingView)
self.loadingView.frame = CGRect(origin: .zero, size:self.view.frame.size)
.The Implementation is as below,
final class LoaderView: UIView {
fileprivate let spinner = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
fileprivate let effect = UIBlurEffect(style: .light)
fileprivate let backgroundView = UIVisualEffectView(frame:.zero)
override init(frame: CGRect) {
spinner.startAnimating()
super.init(frame: frame)
backgroundView.effect = effect
addSubview(backgroundView)
addSubview(spinner)
}
#available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setSpinnerColor(color:UIColor){
spinner.color = color
}
override func layoutSubviews() {
super.layoutSubviews()
backgroundView.frame = CGRect(x: 0, y: 0, width: self.bounds.height/5, height: self.bounds.height/5)
backgroundView.center = CGPoint(x:self.bounds.size.width / 2.0, y: self.bounds.size.height / 2.0)
backgroundView.clipsToBounds = true
backgroundView.cornerRadius = 10
spinner.center = center
}
}

Related

How to make a transparent hole in UIVisualEffectView?

I have a simple UIViewController with UIVisualEffectView presented over another controller using overCurrentContext.
and it is fine.
now I try to make a hole inside that view the following way:
class CoverView: UIView {
private let blurView: UIVisualEffectView = {
UIVisualEffectView(effect: UIBlurEffect(style: .dark))
}()
// MARK: - Internal
func setup() {
addSubview(blurView)
blurView.snp.makeConstraints { maker in
maker.edges.equalToSuperview()
}
blurView.makeClearHole(rect: CGRect(x: 100, y: 100, width: 100, height: 230))
}
}
extension UIView {
func makeClearHole(rect: CGRect) {
let maskLayer = CAShapeLayer()
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
maskLayer.fillColor = UIColor.black.cgColor
let pathToOverlay = UIBezierPath(rect: bounds)
pathToOverlay.append(UIBezierPath(rect: rect))
pathToOverlay.usesEvenOddFillRule = true
maskLayer.path = pathToOverlay.cgPath
layer.mask = maskLayer
}
}
But the effect is reversed than I expected, why?
I need everything around blurred the way how rectangle currently is. And the rect inside should be transparent.
EDIT::
I have studied everything from comments below, and tried another answer, but result is still the same. Why?;) I have no idea what is wrong.
private func makeClearHole(rect: CGRect) {
let maskLayer = CAShapeLayer()
maskLayer.fillColor = UIColor.black.cgColor
let pathToOverlay = CGMutablePath()
pathToOverlay.addRect(blurView.bounds)
pathToOverlay.addRect(rect)
maskLayer.path = pathToOverlay
maskLayer.fillRule = .evenOdd
blurView.layer.mask = maskLayer
}
Well I tested your code and the original code with makeClearHole function in the extension works fine! The problem lies somewhere else.
1- Change the CoverView as following*
class CoverView: UIView {
private lazy var blurView: UIVisualEffectView = {
let bv = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
bv.frame = bounds
return bv
}()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Internal
func setup() {
addSubview(blurView)
blurView.snp.makeConstraints { maker in
maker.edges.equalToSuperview()
}
blurView.makeClearHole(rect: CGRect(x: 100, y: 100, width: 100, height: 230))
}
}
2- Give a frame to coverView in your view controller
The view controller you have is different. But you should give the CoverView instance a frame. This is how: (again, this is how I tested but your view controller is definitely different)
class ViewController: UIViewController {
var label: UILabel!
var coverView: CoverView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
label = UILabel()
label.text = "HELLO WORLD"
label.font = UIFont.systemFont(ofSize: 40, weight: .black)
coverView = CoverView(frame: CGRect(x: 20, y: 200, width: 300, height: 400))
view.addSubview(label)
view.addSubview(coverView)
label.snp.makeConstraints { make in
make.center.equalTo(view)
}
coverView.snp.makeConstraints { make in
make.width.equalTo(coverView.bounds.width)
make.height.equalTo(coverView.bounds.height)
make.leading.equalTo(view).offset(coverView.frame.minX)
make.top.equalTo(view).offset(coverView.frame.minY)
}
}
}
** Result**

Background Colour in UIView not showing

I have a UIView that should show two colours, red and orange, based on the value of rating: Double The problem is that when I run the app nothing is showing up. In my output log the function prints that it has run and that the rating value is what it should be. So I am not sure why nothing is showing up when I run the app, I just see white.
class RatingViewController: UIView {
var rating: Double = 1.0
var rate: Double? {
didSet {
rating = rate!
setUpView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func setUpView() {
Self.backgroundcolor = UIColor.yellow
print("rating is \(rating), and width is \((UIScreen.main.bounds.width * CGFloat(rating/10)))")
let width = (UIScreen.main.bounds.width * CGFloat(rating/10))
var view: UIView
view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.size.height))
view.backgroundColor = UIColor.red
self.addSubview(view)
}
}
You have to add setUpView in init(: method
class RatingViewController: UIView {
var rating: Double = 1.0
required init?(coder: NSCoder) {
super.init(coder: coder)
}
init(frame: CGRect, rate: Double) {
super.init(frame: frame)
self.rating = rate
setUpView()
}
private func setUpView() {
self.backgroundColor = UIColor.yellow
print("rating is \(rating), and width is \((UIScreen.main.bounds.width * CGFloat(rating/10)))")
let width = (UIScreen.main.bounds.width * CGFloat(rating/10))
var view: UIView
view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.size.height))
view.backgroundColor = UIColor.red
self.addSubview(view)
}
}
Now you can call this class with custom init method like that:
// chnage frame and rate according to your requirment
let rView = RatingViewController(frame: CGRect(x: 0, y: 0, width: 320, height: 300), rate: 2.0)

CAShapeLayer clipped by image view clip to bounds

I am trying to customize UIImage view to include a red/green circle to show online status. My current code is like so:
import UIKit
class AvatarImageView: UIImageView {
enum AvatarImageViewOnlineStatus {
case online
case offline
case hidden
}
var onlineStatus: AvatarImageViewOnlineStatus = .hidden
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
clipsToBounds = true
layer.cornerRadius = frame.size.width / 2.0
let circleShapeLayer: CAShapeLayer = CAShapeLayer()
circleShapeLayer.path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 30, height: 30)).cgPath
circleShapeLayer.fillColor = UIColor.red.cgColor
layer.addSublayer(circleShapeLayer)
}
}
However the layer is also clipped:
maskToBounds on the layer is by default set to false the docs state:
A Boolean indicating whether sublayers are clipped to the layer’s bounds. Animatable.
However it is still clipped. What am I doing wrong here?

Animate setFillColor color change in custom UIView

I have a custom UIView called CircleView which is essentially a colored ellipse. The color property I'm using to color the ellipse is rendered using setFillColor on the graphics context. I was wondering if there was a way to animate the color change, because when I run through the animate / transition the color changes immediately instead of being animated.
Example Setup
let c = CircleView()
c.frame = CGRect(x: 20, y: 20, width: 100, height: 100)
c.color = UIColor.blue
c.backgroundColor = UIColor.clear
self.view.addSubview(c)
UIView.transition(with: c, duration: 5, options: .transitionCrossDissolve, animations: {
c.color = UIColor.red // Not animated
})
UIView.animate(withDuration: 5) {
c.color = UIColor.yellow // Not animated
}
Circle View
class CircleView : UIView {
var color = UIColor.blue {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {return}
context.addEllipse(in: rect)
context.setFillColor(color.cgColor)
context.fillPath()
}
}
You can use the built in animation support for the layer's backgroundColor.
While the easiest way to make a circle is to make your view a square (using aspect ratio constraints, for instance) and then set the cornerRadius to half the width or height, I assume you want something a bit more advanced, and that is why you used a path.
My solution to this would be something like:
class CircleView : UIView {
var color = UIColor.blue {
didSet {
layer.backgroundColor = color.cgColor
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
// Setup the view, by setting a mask and setting the initial color
private func setup(){
layer.mask = shape
layer.backgroundColor = color.cgColor
}
// Change the path in case our view changes it's size
override func layoutSubviews() {
super.layoutSubviews()
let path = CGMutablePath()
// add an elipse, or what ever path/shapes you want
path.addEllipse(in: bounds)
// Created an inverted path to use as a mask on the view's layer
shape.path = UIBezierPath(cgPath: path).reversing().cgPath
}
// this is our shape
private var shape = CAShapeLayer()
}
Or if you really need a simple circle, just something like:
class CircleView : UIView {
var color = UIColor.blue {
didSet {
layer.backgroundColor = color.cgColor
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup(){
clipsToBounds = true
layer.backgroundColor = color.cgColor
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.height / 2
}
}
Either way, this will animate nicely:
UIView.animate(withDuration: 5) {
self.circle.color = .red
}
Strange things happens!
Your code is ok, you just need to call your animation in another method and asyncronusly
As you can see, with
let c = CircleView()
override func viewDidLoad() {
super.viewDidLoad()
c.frame = CGRect(x: 20, y: 20, width: 100, height: 100)
c.color = UIColor.blue
c.backgroundColor = UIColor.clear
self.view.addSubview(c)
changeColor()
}
func changeColor(){
DispatchQueue.main.async
{
UIView.transition(with: self.c, duration: 5, options: .transitionCrossDissolve, animations: {
self.c.color = UIColor.red // Not animated
})
UIView.animate(withDuration: 5) {
self.c.color = UIColor.yellow // Not animated
}
}
}
Work as charm.
Even if you add a button that trigger the color change, when you press the button the animation will work.
I encourage you to set this method in the definition of the CircleView
func changeColor(){
DispatchQueue.main.async
{
UIView.transition(with: self, duration: 5, options: .transitionCrossDissolve, animations: {
self.color = UIColor.red
})
UIView.animate(withDuration: 5) {
self.color = UIColor.yellow
}
}
}
and call it where you want in your ViewController, simply with
c.changeColor()

IBDesignable inside UITableViewCell not rendered properly

I have a grid of custom "text below image" UIButtons inside a static UITableViewCell.
To make 8 UIButtons form 4x2 grid, I put them into 2 horizontal UIStackViews and wrapped them with a vertical UIStackView.
You can see details in the screenshots below.
It runs perfect on simulator and real devices, but not rendered properly in Storyboard. I'd like to know if these are something missing in my code or some settings I should check/uncheck in Storyboard.
Runtime behavior(works well)
In Storyboard(chaos)
Outline
Custom Button:
import UIKit
#IBDesignable class YPTextBelowImageButton: UIButton {
#IBInspectable var heightRatio: CGFloat = 0.8 {
didSet {
self.setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.frame = frame
self.imageView?.contentMode = .scaleAspectFit
self.titleLabel?.textAlignment = .center
self.setNeedsDisplay()
self.setNeedsLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.imageView?.contentMode = .scaleAspectFit
self.titleLabel?.textAlignment = .center
self.setNeedsDisplay()
self.setNeedsLayout()
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRect(x: 0, y: 0, width: contentRect.size.width, height: contentRect.size.height * heightRatio)
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRect(x: 0, y: contentRect.size.height * heightRatio, width: contentRect.size.width, height: contentRect.size.height * (1 - heightRatio))
}
}
Preview:
func prepareForInterfaceBuilder not working
override func prepareForInterfaceBuilder() {
self.imageView?.contentMode = .scaleAspectFit
self.titleLabel?.textAlignment = .center
self.setNeedsDisplay()
self.setNeedsLayout()
}

Resources