Swift function not drawing circle in updated view - swift

I have a stepper in my View Controller that updates variables(redd1, greenn1, bluee1) in my UIView. drawRect draws an initial circle and updateColor is meant to draw a new circle on top of it with an updated color. The variables get updated when I call updateColor and I know that they get passed through because when i have their values printed out in updateColor they are correct. updateColor won't draw a new circle.
class UIView1: UIView {
var redd1 = 0.0;
var greenn1 = 0.0;
var bluee1 = 0.0;
override init(frame: CGRect)
{
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect)
{
let circle2 = UIView(frame: CGRect(x: -25.0, y: 10.0, width: 100.0, height:100.0))
circle2.layer.cornerRadius = 50.0
let startingColor2 = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat (greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle2.backgroundColor = startingColor2;
addSubview(circle2);
}
func updateColor()
{
let circle = UIView(frame: CGRect(x: -25.0, y: 10.0, width: 100.0, height: 100.0))
circle.layer.cornerRadius = 50.0;
let startingColor = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat(greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle.backgroundColor = startingColor;
addSubview(circle);
}
}

Do not call addSubview in drawRect. Only use drawRect if you were going to draw a circle yourself (e.g. by calling stroke of a UIBezierPath). Or if you're going to add circles as subviews (as CAShapeLayer sublayers to the view's layer), the retire drawRect altogether.
But by calling addSubview in updateColor, you're not changing the color, but rather you're adding another subview every time. Likewise, by calling addSubview in drawRect, you're adding another subview there, too, every time the OS calls drawRect. For example, I ran your code, changing the color from black to red, to green, to blue, and triggered drawRect to be called again a few more times, and when I look at the view hierarchy in the view debugger, you can see all of the views in the view hierarchy:
This is a dangerous practice, because those subviews will add up over time, taking up memory.
If you're going to add subviews, I would suggest (a) drawRect is not the right place to be adding subviews; and (b) if you're determined to go the addSubview approach, decide whether you can remove the previous subviews before adding new ones.
Personally, if I wanted drawRect to draw circles of different colors, I would just stroke the UIBezierPath. For example:
class CircleView: UIView {
var redd1: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
var greenn1 : CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
var bluee1: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
override func drawRect(rect: CGRect) {
let color = UIColor(red: redd1 / 255.0, green: greenn1 / 255.0, blue: bluee1 / 255.0, alpha: 1.0)
color.setStroke()
let path = UIBezierPath(arcCenter: CGPoint(x: 75, y: 75), radius: 50, startAngle: 0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
path.lineWidth = 2
path.lineCapStyle = .Round
path.stroke()
}
}
By the way, you notice that we can retire updateColor entirely. With the above code, if you set either red, green, or blue, it will call setNeedsDisplay, which will trigger the view being redrawn automatically.
You report that the above isn't working. Well, I tried it with this code:
class ViewController: UIViewController {
#IBOutlet weak var circleView: CircleView!
#IBOutlet weak var redSlider: UISlider!
#IBOutlet weak var greenSlider: UISlider!
#IBOutlet weak var blueSlider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
redSlider.value = Float(circleView.redd1) / 255.0
greenSlider.value = Float(circleView.greenn1) / 255.0
blueSlider.value = Float(circleView.bluee1) / 255.0
}
#IBAction func valueChangedForSlider(sender: UISlider) {
circleView.redd1 = 255.0 * CGFloat(redSlider.value)
circleView.greenn1 = 255.0 * CGFloat(greenSlider.value)
circleView.bluee1 = 255.0 * CGFloat(blueSlider.value)
}
}
And it worked fine:

As you mentioned that the both have the values which they should have.
The two circles which you are drawing are exactly on the same postion and both have exactly the same color according to your code snippet. How should they differ? Of course you can't see the two circles when they are having the same color and same position. They are overlayed.

Related

Swift shadow does not spread correctly

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

UICollectionviewcell badge masked behind view

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

UIView with corner Radius and Shadow view doesn't clip subviews in corners

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]

my drawRect function will not update

I want the values for the colors of the circle to update when the variables(redd1,greenn1,bluee1) are changed by the steppers in my ViewController. The original circle is drawn by drawRect.
var redd1 = 0.0;
var greenn1 = 0.0;
var bluee1 = 0.0;
override init(frame: CGRect)
{
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect)
{
let circle2 = UIView(frame: CGRect(x: -25.0, y: 10.0, width: 100.0, height:100.0))
circle2.layer.cornerRadius = 50.0
let startingColor2 = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat (greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle2.backgroundColor = startingColor2;
addSubview(circle2);
}
I tried creating a new function that draws a new circle on top of the old one. It is called by the stepper. The new function, updateColor(), receives the new value from the stepper. This partially works because it prints out the correct new values, but never draws the new circle.
func updateColor()
{
let circle = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
circle.layer.cornerRadius = 50.0;
let startingColor = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat(greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle.backgroundColor = startingColor;
addSubview(circle);
}
I'm not so good in Swift, but that's a simple way how i wood do it:
var circle2: UIView
override func drawRect(rect: CGRect) {
circle2 = UIView(frame: CGRect(x: -25.0, y: 10.0, width: 100.0, height:100.0))
circle2.layer.cornerRadius = 50.0
let startingColor2 = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat (greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle2.backgroundColor = startingColor2;
addSubview(circle2);
}
#IBAction valueChanged(sender: UIStepper) {
changeColor(color(the Color you want), Int(the Int value you want the color to change at))
}
func changeColor(color: UIColor, number: Int) {
if stepper.value == number {
circle2.backgroundColor = color
}
}
You have to create a IBAction for the stepper of the type value changed and call the method for every color and to it belonging Int in the IBAction for the stepper. So every time + or - on the stepper is pressed the IBAction calls the changeColor method and the changeColor method tests which color should be set to circle2.

iOS8: Auto-layout and Gradient

Setup:
I have a View Controller that consists of a View and a Container View.
The View (Orange) is pinned to top 0, left 0, and right 0.
The Container View (Gray) is pinned to bottom 0, left 0, and right 0.
The View's Bottom Space to: Container View = 0
The View's Proportional Height to Container View = 1
Desired Results:
I would like to add gradient to the background of the View (Orange)
Tried:
I'm using Auto-layout with class sizes to get different behavior on different screen.
Code:
class ViewController: UIViewController {
#IBOutlet weak var graphView: UIView!
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let backgroundColor = CAGradientLayer().graphViewBackgroundColor()
backgroundColor.frame = self.graphView.frame
self.graphView.layer.addSublayer(backgroundColor)
}
I have a category:
extension CAGradientLayer {
func graphViewBackgroundColor() -> CAGradientLayer {
let topColor = UIColor(red: (160/255.0), green: (160/255.0), blue: (160/255.0), alpha: 1)
let bottomColor = UIColor(red: (52/255.0), green: (53/255.0), blue: (52/255.0), alpha: 1)
let gradientColors: [CGColor] = [topColor.CGColor, bottomColor.CGColor]
let gradientLocations: [Float] = [0.0, 1.0]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations
return gradientLayer
}
}
Result:
As you can see gradient did not cover the entire View.
Question: How can I get the gradient to cover the entire View
Update:
When I place the code in viewDidLayoutSubviews() It looks weird when I rotate:
Simply do it this inside viewDidLayoutSubviews:
override func viewDidLayoutSubview() {
super.viewDidLayoutSubviews
backgroundColor.frame = self.graphView.bounds
}
viewDidLayoutSubviews should be called when you rotate the device.
If it is not called, override this method and do it as,
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
backgroundColor.frame = self.graphView.bounds
}
Try putting your gradient code into viewDidLayoutSubviews instead of viewDidLoad
When viewDidLoad is called the views are not laid out (ie do not have their final frames set yet), so this is why you are only seeing a partial coverage of the gradient