After CAReplicatorLayer animation in a `vc`'s `subview`, switch `vc` comes a strange issue - ios

CAReplicator did not keep the state after the switch vc:
Dots of CAReplicator did not keep its scale after the vc switch back.
As you see, the circle animation is created by CAReplicator.
after the main vc switch to another vc, then switch back, the Circle's dots become very small. witch is set in the initial.
My code is below:
In the main vc:
func initUI() {
let lml_frame = CGRect.init(x: 0, y: 64, width: self.view.bounds.size.width, height: 400)
lml_digtal_view = LMLDigitalDazzleAnimationView.init(frame: lml_frame)
self.view.addSubview(lml_digtal_view!)
}
In the LMLDigitalDazzleAnimationView:
import Foundation
import UIKit
class LMLDigitalDazzleAnimationView: UIView {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
var initFrame = CGRect.init(x: 0, y: 0, width: 320, height: 480)
var fromColor = UIColor.init(red: 240/255.0, green: 77.0/255.0, blue: 48.0/255.0, alpha: 1.0).cgColor
var toColor = UIColor.init(red: 220.0/255.0, green: 28.0/255.0, blue: 44.0/255.0, alpha: 1.0).cgColor
var money:Float? = 1200.25 {
didSet {
}
}
override init(frame: CGRect) {
super.init(frame: frame)
initFrame = frame
initUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func initUI(){
let gradul_layer = CAGradientLayer.init()
gradul_layer.frame = CGRect.init(x: 0, y: 0, width: initFrame.width, height: initFrame.height)
gradul_layer.colors = [
fromColor,
toColor
]
gradul_layer.startPoint = CGPoint.init(x: 0.5, y: 0.3)
gradul_layer.endPoint = CGPoint.init(x: 0.5, y: 0.7)
layer.addSublayer(gradul_layer)
let wave_view0 = KHWaveView.init(frame: CGRect.init(x: 0, y: initFrame.height - 80, width: initFrame.width, height: 80))
//wave_view.backgroundColor = UIColor.white
wave_view0.waveColor = UIColor.init(red: 1, green: 1, blue: 1, alpha: 0.5)
wave_view0.waveSpeed = 1.3
wave_view0.waveTime = 0
wave_view0.wave()
self.addSubview(wave_view0)
let wave_view = KHWaveView.init(frame: CGRect.init(x: 0, y: initFrame.height - 80, width: initFrame.width, height: 80))
//wave_view.backgroundColor = UIColor.white
wave_view.waveColor = UIColor.white
wave_view.waveSpeed = 1.0
wave_view.waveTime = 0
wave_view.wave()
self.addSubview(wave_view)
animateCircle()
animateDigitalIcrease(money: money!)
}
func animateCircle() -> Void {
let r = CAReplicatorLayer()
r.bounds = CGRect(x:0.0, y:0.0, width:260.0, height:260.0)
r.cornerRadius = 10.0
r.backgroundColor = UIColor.clear.cgColor
r.position = CGPoint.init(x: self.bounds.width / 2.0, y: 160)
self.layer.addSublayer(r)
let dot = CALayer()
dot.bounds = CGRect(x:0.0, y :0.0, width:6.0, height:6.0)
dot.position = CGPoint(x:100.0, y:10.0)
dot.backgroundColor = UIColor(white:1, alpha:1.0).cgColor
dot.cornerRadius = 3.0
r.addSublayer(dot)
let nrDots: Int = 32
r.instanceCount = nrDots
let angle = CGFloat(2*M_PI) / CGFloat(nrDots)
r.instanceTransform = CATransform3DMakeRotation(angle, 0.1, 0.1, 1.0)
let duration:CFTimeInterval = 1.5
let shrink = CABasicAnimation(keyPath: "transform.scale")
shrink.fromValue = 1.0
shrink.toValue = 1.0 // 0.5
shrink.duration = duration
shrink.repeatCount = Float.infinity
dot.add(shrink, forKey: nil)
r.instanceDelay = duration/Double(nrDots)
dot.transform = CATransform3DMakeScale(0.1, 0.1, 0.1)
delay(delay: duration) {
let turn_key_path = "transform.rotation"
let turn_ani = CABasicAnimation.init(keyPath: turn_key_path)
turn_ani.isRemovedOnCompletion = false
turn_ani.fillMode = kCAFillModeForwards
turn_ani.toValue = M_PI*2
turn_ani.duration = 2.0
turn_ani.repeatCount = 2
r.add(turn_ani, forKey: turn_key_path)
}
}
func delay(delay:Double, closure:#escaping ()->()){
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
func animateDigitalIcrease(money :Float){
let frame = CGRect.init(x: 0, y: 0, width: 120, height: 80)
let counterLabel = LMLDigitalIncreaseLabel.init(frame: frame, andDuration: 2.0, andFromValue: 0, andToValue: money)
counterLabel?.center = CGPoint.init(x: self.bounds.size.width / 2.0, y: 130)
self.addSubview(counterLabel!)
counterLabel?.start()
delay(delay: 5.0) {
counterLabel?.stop()
self.animateFadeShowSmallMoney()
}
}
func animateFadeShowSmallMoney(){
let border_view = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 30))
border_view.layer.cornerRadius = 15
border_view.layer.masksToBounds = true
border_view.layer.borderWidth = 1
border_view.backgroundColor = UIColor.clear
border_view.layer.borderColor = UIColor.white.cgColor
let small_money_frame = CGRect.init(x: 0, y: 0, width: 80, height: 30)
let small_money = UILabel.init(frame: small_money_frame)
small_money.center = border_view.center
small_money.adjustsFontSizeToFitWidth = true
small_money.textAlignment = NSTextAlignment.center
small_money.text = "mo:" + String(format:"%.2f", money!)
small_money.textColor = UIColor.white
border_view.addSubview(small_money)
border_view.alpha = 0.0
self.addSubview(border_view)
border_view.center = CGPoint.init(x: self.bounds.size.width/2.0, y: 220)
UIView.animate(withDuration: 1.0) {
border_view.alpha = 1.0
}
}
}
My code is not good, you can advice me how to encapsulate a animation class better.

After many attention, I solve my issue:
delay(delay: duration) {
let turn_key_path = "transform.rotation"
let turn_ani = CABasicAnimation.init(keyPath: turn_key_path)
turn_ani.isRemovedOnCompletion = false
turn_ani.fillMode = kCAFillModeForwards
turn_ani.toValue = M_PI*2
turn_ani.duration = 2.0
turn_ani.repeatCount = 2
r.add(turn_ani, forKey: turn_key_path)
dot.transform = CATransform3DMakeScale(1, 1, 1) // add this line solve my issue.
}

Related

How can I use the same button and action multiple times, but affect the layer instead of the view

I am trying to use the same button in each createCircle() function, but when I press a button and it runs the handleTap() function, it only applies to the most recently added circle. I would like to use the same button but when I click on an individual button it should run the animation on the one I pressed.
import UIKit
class SecondViewController: UIViewController {
let shapeLayer = CAShapeLayer()
let percentageLabel: UILabel = {
let label = UILabel()
label.text = ""
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 28)
label.textColor = UIColor(red: 0.59, green: 0.42, blue: 0.23, alpha: 1.00)
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
_ = createCircle(positionX: 100, positionY: 200)
_ = createCircle(positionX: 100, positionY: 375)
_ = createCircle(positionX: 100, positionY: 550)
_ = createCircle(positionX: 100, positionY: 725)
_ = createCircle(positionX: 300, positionY: 200)
_ = createCircle(positionX: 300, positionY: 375)
_ = createCircle(positionX: 300, positionY: 550)
_ = createCircle(positionX: 300, positionY: 725)
}
//MARK: - Create Circle
func createCircle(positionX: Int, positionY: Int) -> (CGPoint) {
let trackLayer = CAShapeLayer()
let button = UIButton(type: .custom)
button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.clipsToBounds = true
button.center = view.center
button.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
view.addSubview(button)
let circularPath = UIBezierPath(arcCenter: .zero, radius: 50, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor(red: 0.82, green: 0.69, blue: 0.52, alpha: 1.00).cgColor
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineWidth = 10
trackLayer.position = CGPoint(x: positionX, y: positionY)
view.layer.addSublayer(trackLayer)
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor(red: 0.59, green: 0.42, blue: 0.23, alpha: 1.00).cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 10
shapeLayer.lineCap = CAShapeLayerLineCap.round
shapeLayer.position = CGPoint(x: positionX, y: positionY)
shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi / 2, 0, 0, 1)
view.addSubview(percentageLabel)
percentageLabel.frame = CGRect(x: 0, y: 0, width: 150, height: 150)
percentageLabel.center = CGPoint(x: positionX, y: positionY)
return CGPoint(x: positionX, y: positionY)
}
//MARK: - Tap function
var done = 0
var toDo = 0
#objc func handleTap(sender: UIButton) {
toDo = 5
if done < toDo {
done += 1
} else {
done -= toDo
}
let percentage = CGFloat(done) / CGFloat(toDo)
percentageLabel.text = "\(Int(percentage * 100))%"
DispatchQueue.main.async {
self.shapeLayer.strokeEnd = percentage
}
view.layer.addSublayer(shapeLayer)
}
}
This is a picture of what happens when I run my code. When I click on any of the pictured buttons, instead of running the progress bar animation on whichever button I pressed, it only runs it on the button that was added last to the view controller.
Instead of using createCircle() in your view controller, make a new file for it and initialize the function like this inside.
import UIKit
class Button: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
createCircle()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let percentageLabel: UILabel = {
let label = UILabel()
label.text = ""
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 28)
label.textColor = UIColor(red: 0.59, green: 0.42, blue: 0.23, alpha: 1.00)
return label
}()
let shapeLayer = CAShapeLayer()
func createCircle() {
let trackLayer = CAShapeLayer()
let button = UIButton(type: .custom)
button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.clipsToBounds = true
button.center = center
button.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
addSubview(button)
let circularPath = UIBezierPath(arcCenter: .zero, radius: 50, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor(red: 0.82, green: 0.69, blue: 0.52, alpha: 1.00).cgColor
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineWidth = 10
trackLayer.position = center
layer.addSublayer(trackLayer)
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor(red: 0.59, green: 0.42, blue: 0.23, alpha: 1.00).cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 10
shapeLayer.lineCap = CAShapeLayerLineCap.round
shapeLayer.position = center
shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi / 2, 0, 0, 1)
addSubview(percentageLabel)
percentageLabel.frame = CGRect(x: 0, y: 0, width: 150, height: 150)
// percentageLabel.center = CGPoint(x: positionX, y: positionY)
// return CGPoint(x: positionX, y: positionY)
}
var done = 0
var toDo = 0
#objc func handleTap(sender: UIButton) {
toDo = 5
if done < toDo {
done += 1
} else {
done -= toDo
}
let percentage = CGFloat(done) / CGFloat(toDo)
percentageLabel.text = "\(Int(percentage * 100))%"
DispatchQueue.main.async {
self.shapeLayer.strokeEnd = percentage
}
layer.addSublayer(shapeLayer)
}
}
You can remove the code from your secondViewController that has to do with your button and call the circle function inside of your viewDidLoad(). The problem was that your animation was affecting your view (not an individual circle), by creating a class for your circle button, you can call it in your secondViewController and it will affect only the circle in which the button was pressed.

CoreImageContext's CreateCGImage producing wrong CGRect

Code:
enum GradientDirection {
case up
case left
case upLeft
case upRight
}
extension SKTexture {
convenience init(size: CGSize, color1: CIColor, color2: CIColor, direction: GradientDirection = .up) {
let coreImageContext = CIContext(options: nil)
let gradientFilter = CIFilter(name: "CILinearGradient")
gradientFilter!.setDefaults()
var startVector:CIVector
var endVector:CIVector
switch direction {
case .up:
startVector = CIVector(x: size.width/2, y: 0)
endVector = CIVector(x: size.width/2, y: size.height)
case .left:
startVector = CIVector(x: size.width, y: size.height/2)
endVector = CIVector(x: 0, y: size.height/2)
case .upLeft:
startVector = CIVector(x: size.width, y: 0)
endVector = CIVector(x: 0, y: size.height)
case .upRight:
startVector = CIVector(x: 0, y: 0)
endVector = CIVector(x: size.width, y: size.height)
}
gradientFilter!.setValue(startVector, forKey: "inputPoint0")
gradientFilter!.setValue(endVector, forKey: "inputPoint1")
gradientFilter!.setValue(color1, forKey: "inputColor0")
gradientFilter!.setValue(color2, forKey: "inputColor1")
let imgRect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
let cgimg = coreImageContext.createCGImage(gradientFilter!.outputImage!, from: imgRect)!
print("cgimg: ", cgimg) // *** Observer this output ***** 103.0 width and height
self.init(cgImage: cgimg)
}
}
Calling Initializer:
// e.g. CGSize(width: 102.69999694824219, height: 102.69999694824219)
let textureSize = CGSize(width: self.frame.width, height: self.frame.height)
let shapeTexture = SKTexture(size: textureSize, color1: bottomColor, color2: topColor, direction: .upRight)
Passing width/height: 102.69999694824219, produces shapeTexture with width/height: 103.
Seems like coreImageContext.createCGImage is rounding off 102.69999694824219 to 103.0.
This results in minor unexpected output. How can I by-pass this rounding off? Or is there is any other method to generate Gradient image for Nodes?
More Code:
class BubbleNode: SKShapeNode {
private var backgroundNode: SKCropNode!
var label: SKLabelNode!
private var state: BubbleNodeState!
private let BubbleAnimationDuration = 0.2
private let BubbleIconPercentualInset = 0.4
var model: BubbleModel! {
didSet {
self.label.text = model.name
}
}
override init() {
super.init()
}
convenience init(withRadius radius: CGFloat) {
self.init()
self.init(circleOfRadius: radius)
state = .normal
self.configure()
}
private func configure() {
self.name = "mybubble"
physicsBody = SKPhysicsBody(circleOfRadius: 4 + self.path!.boundingBox.size.width / 2.0)
physicsBody!.isDynamic = true
physicsBody!.affectedByGravity = false
physicsBody!.allowsRotation = false
physicsBody!.mass = 0.3
physicsBody!.friction = 0.0
physicsBody!.linearDamping = 3
backgroundNode = SKCropNode()
backgroundNode.isUserInteractionEnabled = false
backgroundNode.position = CGPoint.zero
backgroundNode.zPosition = 0
self.addChild(backgroundNode)
label = SKLabelNode(fontNamed: "")
label.preferredMaxLayoutWidth = self.frame.size.width - 16
label.numberOfLines = 0
label.position = CGPoint.zero
label.fontColor = .white
label.fontSize = 10
label.isUserInteractionEnabled = false
label.verticalAlignmentMode = .center
label.horizontalAlignmentMode = .center
label.zPosition = 2
self.addChild(label)
}
func addGradientNode(withRadius radius: CGFloat) {
let gradientNode = SKShapeNode(path: self.path!)
gradientNode.zPosition = 1
gradientNode.fillColor = .white
gradientNode.strokeColor = .clear
let bottomColor = CIColor(red: 0.922, green: 0.256, blue: 0.523, alpha: 1)
let topColor = CIColor(red: 0.961, green: 0.364, blue: 0.155, alpha: 1)
let textureSize = CGSize(width: self.frame.width, height: self.frame.height)
let shapeTexture = SKTexture(size: textureSize, color1: bottomColor, color2: topColor, direction: .upRight)
gradientNode.fillTexture = shapeTexture
self.addChild(gradientNode)
print("path: ", self.path!)
print("textureSize: ", textureSize)
print("shapeTexture: ", shapeTexture)
}
}
Any image/texture will always have integer sizes since there are no sub-pixels in memory. So frameworks like Core Image will always round the given size up to the next integer value.
In contrast, the frame of a view is given in points, which need to be multiplied by the view's contentScaleFactor to get the actual pixel size (that you should use to generate your gradient). UIKit also allows for sub-pixel frame sizes, but under the hood, it will also round up when rendering the views to the screen.

How to draw multiple horizontally circles in rectangle (UIButton or UIControl) Swift iOS

How to draw about three circle in horizontally area with main and ring color in rectangle. I need to create custom button with this circles, something like this:
Is there any good way to do this?
We can design such kind of views with UIStackView in very ease manner.
Take a stackView, set its alignment to center, axis to horizontal and distribution to fill. Create a UILabel/UIButton/UIImageView or even UIView and add rounded radius and border to it. Finally, add those views to the main stackView.
Try this.
override func viewDidLoad() {
super.viewDidLoad()
//Setup stackView
let myStackView = UIStackView()
myStackView.axis = .horizontal
myStackView.alignment = .center
myStackView.distribution = .fillEqually
myStackView.spacing = 8
view.addSubview(myStackView)
//Setup circles
let circle_1 = circleLabel()
let circle_2 = circleLabel()
let circle_3 = circleLabel()
myStackView.addArrangedSubview(circle_1)
myStackView.addArrangedSubview(circle_2)
myStackView.addArrangedSubview(circle_3)
myStackView.translatesAutoresizingMaskIntoConstraints = false
myStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0).isActive = true
myStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0).isActive = true
}
func circleLabel() -> UILabel {
let label = UILabel()
label.backgroundColor = UIColor.red
label.layer.cornerRadius = 12.5
label.layer.masksToBounds = true
label.layer.borderColor = UIColor.orange.cgColor
label.layer.borderWidth = 3.0
label.widthAnchor.constraint(equalToConstant: 25.0).isActive = true
label.heightAnchor.constraint(equalToConstant: 25.0).isActive = true
return label
}
To make a Single Circle like that, you need to make use of UIBezierPath and CAShapeLayer .
let outerCirclePath = UIBezierPath(arcCenter: CGPoint(x: 100,y: 100), radius: CGFloat(50), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
let outerCircleShapeLayer = CAShapeLayer()
outerCircleShapeLayer.path = outerCirclePath.cgPath
outerCircleShapeLayer.fillColor = UIColor.white.cgColor
outerCircleShapeLayer.lineWidth = 3.0
view.layer.addSublayer(outerCircleShapeLayer)
// Drawing the inner circle
let innerCirclePath = UIBezierPath(arcCenter: CGPoint(x: 100,y: 100), radius: CGFloat(40), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
let innerCircleShapeLayer = CAShapeLayer()
innerCircleShapeLayer.path = innerCirclePath.cgPath
innerCircleShapeLayer.fillColor = UIColor.blue.cgColor
view.layer.addSublayer(innerCircleShapeLayer)
I have attached an image below for the Playground version of it .
Just play around with arcCenter and radius values and you will get the desired output
My team helped me and here is solution to create this with dynamically changing state of circles (with different stroke and fill colors):
import UIKit
#IBDesignable
class CirclesButton: UIControl {
#IBInspectable
var firstCircle: Bool = false {
didSet {
setNeedsDisplay()
}
}
#IBInspectable
var secondCircle: Bool = false {
didSet {
setNeedsDisplay()
}
}
#IBInspectable
var thirdCircle: Bool = false {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
// get context
guard let context = UIGraphicsGetCurrentContext() else { return }
// make configurations
context.setLineWidth(1.0);
context.setStrokeColor(UIColor.white.cgColor)
context.setFillColor(red: 0.0, green: 0.58, blue: 1.0, alpha: 1.0)
// find view center
let dotSize:CGFloat = 11.0
let viewCenter = CGPoint(x: rect.midX, y: rect.midY)
// find personal dot rect
var dotRect = CGRect(x: viewCenter.x - dotSize / 2.0, y: viewCenter.y - dotSize / 2.0, width: dotSize, height: dotSize)
if secondCircle {
context.fillEllipse(in: dotRect)
}
context.strokeEllipse(in: dotRect)
// find global notes rect
dotRect = CGRect(x: viewCenter.x - dotSize * 1.5 - 4.0, y: viewCenter.y - dotSize / 2.0, width: dotSize, height: dotSize)
if firstCircle {
context.fillEllipse(in: dotRect)
}
context.strokeEllipse(in: dotRect)
// find music rect
dotRect = CGRect(x: viewCenter.x + dotSize / 2.0 + 4.0, y: viewCenter.y - dotSize / 2.0, width: dotSize, height: dotSize)
if thirdCircle {
context.setFillColor(red: 0.0, green: 1.0, blue: 0.04, alpha: 1.0)
context.fillEllipse(in: dotRect)
}
context.strokeEllipse(in: dotRect)
}
}
It will looks like: CirclesButton
Сode:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let buttonSize: CGFloat = 80
let firstButton = CustomButton(position: CGPoint(x: 0, y: 0), size: buttonSize, color: .blue)
self.view.addSubview(firstButton)
let secondButton = CustomButton(position: CGPoint(x: firstButton.frame.maxX, y: 0), size: buttonSize, color: .blue)
self.view.addSubview(secondButton)
let thirdButton = CustomButton(position: CGPoint(x: secondButton.frame.maxX, y: 0), size: buttonSize, color: .green)
self.view.addSubview(thirdButton)
}
}
class CustomButton: UIButton {
init(position: CGPoint, size: CGFloat, color: UIColor) {
super.init(frame: CGRect(x: position.x, y: position.y, width: size, height: size))
self.backgroundColor = color
self.layer.cornerRadius = size / 2
self.clipsToBounds = true
self.layer.borderWidth = 4.0 // make it what ever you want
self.layer.borderColor = UIColor.white.cgColor
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
You can handle button tapped like:
override func viewDidLoad() {
super.viewDidLoad()
firstButton.addTarget(self, action: #selector(handleFirstButton), for: .touchUpInside)
}
#objc func handleFirstButton(sender: UIButton) {
print("first button tapped")
}
Best and Universal Solution for **Button or Label creation (Fully Dynamic)**
var x = 10
var y = 5
var buttonHeight = 40
var buttonWidth = 40
for i in 0..<3 {
let roundButton = UIButton(frame: CGRect(x: x, y: y, width: buttonWidth, height: buttonHeight))
roundButton.setTitle("Butt\(i)", for: .normal)
roundButton.layer.cornerRadius = roundButton.bounds.size.height/2
yourButtonBackView.addSubview(roundButton)
x = x + buttonWidth + 10
if x >= Int(yourButtonBackView.frame.width - 30) {
y = y + buttonHeight + 10
x = 10
}
}

How do i call functions of a class - Swift

Im still trying to learn this portion of things. I've looked around and read a few questions about it but truthfully I dont understand any of it.
I've got a circle class that creates and draws a circle
class CircleView: UIView {
var circleLayer: CAShapeLayer!
var isAnimating = false
override init(frame: CGRect) {
let fColor = UIColor.clear.cgColor
super.init(frame: frame)
self.backgroundColor = UIColor.clear
// Use UIBezierPath as an easy way to create the CGPath for the layer.
// The path should be the entire circle.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.init(rgbColorCodeRed: 230, green: 226, blue: 218, alpha: 1).cgColor
circleLayer.lineWidth = 9.0;
// Don't draw the circle initially
circleLayer.strokeEnd = 0.0
// Add the circleLayer to the view's layer's sublayers
layer.addSublayer(circleLayer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setCircleClockwise(){
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
self.circleLayer.removeFromSuperlayer()
self.circleLayer = formatCirle(circlePath: circlePath)
self.layer.addSublayer(self.circleLayer)
}
func setCircleCounterClockwise(){
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: false)
self.circleLayer.removeFromSuperlayer()
self.circleLayer = formatCirle(circlePath: circlePath)
self.layer.addSublayer(self.circleLayer)
}
func formatCirle(circlePath: UIBezierPath) -> CAShapeLayer{
let circleShape = CAShapeLayer()
circleShape.path = circlePath.cgPath
circleShape.fillColor = UIColor.clear.cgColor
circleShape.strokeColor = UIColor.init(rgbColorCodeRed: 230, green: 226, blue: 218, alpha: 1).cgColor
circleShape.lineWidth = 9.0;
circleShape.strokeEnd = 0.0
return circleShape
}
func animate(duration: TimeInterval){
self.isAnimating = true
self.animateCircleFull(duration: 1)
}
func endAnimate(){
self.isAnimating = false
}
func animateCircleFull(duration: TimeInterval) {
if self.isAnimating{
CATransaction.begin()
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
circleLayer.strokeEnd = 1.0
CATransaction.setCompletionBlock {
self.setCircleCounterClockwise()
self.animateCircleEmpty(duration: duration)
}
// Do the actual animation
circleLayer.add(animation, forKey: "animateCircle")
CATransaction.commit()
}
}
func animateCircleEmpty(duration: TimeInterval){
if self.isAnimating{
CATransaction.begin()
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 1
animation.toValue = 0
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
circleLayer.strokeEnd = 0
CATransaction.setCompletionBlock {
self.setCircleClockwise()
self.animateCircleFull(duration: duration)
}
// Do the actual animation
circleLayer.add(animation, forKey: "animateCircle")
CATransaction.commit()
}
}
Which is being called from my viewController with the below function. It all works fine but what i cant work out is how do i go about calling the endAnimation function on the same circle?
func addCircleView() {
let diceRoll = CGFloat(Int(arc4random_uniform(7))*50)
var circleWidth = CGFloat(100)
var circleHeight = circleWidth
var bgColor: UIColor = UIColor.init(rgbColorCodeRed: 230, green: 226, blue: 218, alpha: 1)
// Create a new CircleView
let circleView = CircleView(frame: CGRect(x: self.view.frame.width/2, y: self.view.frame.height-110, width: circleWidth, height: circleHeight))
//let test = CircleView(frame: CGRect(x: diceRoll, y: 0, width: circleWidth, height: circleHeight))
cv = circleView
view.addSubview(circleView)
// Animate the drawing of the circle over the course of 1 second
circleView.animate(duration: 1)
let imageName = "ButtonBackground.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: self.view.frame.width/2, y: self.view.frame.height-110, width: 100, height: 100)
view.addSubview(imageView)
view.bringSubview(toFront: circleView)
}
For calling endAnimation function on the same circle you have to declare property in your ViewController class.
import UIKit
class ViewController: UIViewController {
var circleView = CircleView()
override func viewDidLoad() {
super.viewDidLoad()
self.addCircleView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func addCircleView() {
let diceRoll = CGFloat(Int(arc4random_uniform(7))*50)
var circleWidth = CGFloat(100)
var circleHeight = circleWidth
var bgColor: UIColor = UIColor.init(rgbColorCodeRed: 230, green: 226, blue: 218, alpha: 1)
// Create a new CircleView
self.circleView = CircleView(frame: CGRect(x: self.view.frame.width/2, y: self.view.frame.height-110, width: circleWidth, height: circleHeight))
//let test = CircleView(frame: CGRect(x: diceRoll, y: 0, width: circleWidth, height: circleHeight))
cv = circleView
view.addSubview(circleView)
// Animate the drawing of the circle over the course of 1 second
circleView.animate(duration: 1)
let imageName = "ButtonBackground.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: self.view.frame.width/2, y: self.view.frame.height-110, width: 100, height: 100)
view.addSubview(imageView)
view.bringSubview(toFront: circleView)
//now you are able to call endAnimation function
self.circleView.endAnimate()
}
}

My indicator is blank [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I use a custom indicator but when i call the subclass indicator in my viewdidload my view controller is blank but when i run it in a playground i can see it in the side window. Here is the code of the indicator. Theres no error but my indicator is not showing. Thats my problem. I would appreciate it if someone could tell me why.
import UIKit
class MaterialLoadingIndicator: UIView {
let MinStrokeLength: CGFloat = 0.05
let MaxStrokeLength: CGFloat = 0.7
let circleShapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.clear
initShapeLayer()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func initShapeLayer() {
circleShapeLayer.actions = ["strokeEnd" : NSNull(),
"strokeStart" : NSNull(),
"transform" : NSNull(),
"strokeColor" : NSNull()]
circleShapeLayer.backgroundColor = UIColor.clear.cgColor
circleShapeLayer.strokeColor = UIColor.blue.cgColor
circleShapeLayer.fillColor = UIColor.clear.cgColor
circleShapeLayer.lineWidth = 5
circleShapeLayer.lineCap = kCALineCapRound
circleShapeLayer.strokeStart = 0
circleShapeLayer.strokeEnd = MinStrokeLength
let center = CGPoint(x: bounds.width*0.5, y: bounds.height*0.5)
circleShapeLayer.frame = bounds
circleShapeLayer.path = UIBezierPath(arcCenter: center,
radius: center.x,
startAngle: 0,
endAngle: CGFloat(M_PI*2),
clockwise: true).cgPath
layer.addSublayer(circleShapeLayer)
}
func startAnimating() {
if layer.animation(forKey: "rotation") == nil {
startColorAnimation()
startStrokeAnimation()
startRotatingAnimation()
}
}
private func startColorAnimation() {
let color = CAKeyframeAnimation(keyPath: "strokeColor")
color.duration = 10.0
color.values = [UIColor(hex: 0x4285F4, alpha: 1.0).cgColor,
UIColor(hex: 0xDE3E35, alpha: 1.0).cgColor,
UIColor(hex: 0xF7C223, alpha: 1.0).cgColor,
UIColor(hex: 0x1B9A59, alpha: 1.0).cgColor,
UIColor(hex: 0x4285F4, alpha: 1.0).cgColor]
color.calculationMode = kCAAnimationPaced
color.repeatCount = Float.infinity
circleShapeLayer.add(color, forKey: "color")
}
private func startRotatingAnimation() {
let rotation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = M_PI*2
rotation.duration = 2.2
rotation.isCumulative = true
rotation.isAdditive = true
rotation.repeatCount = Float.infinity
layer.add(rotation, forKey: "rotation")
}
private func startStrokeAnimation() {
let easeInOutSineTimingFunc = CAMediaTimingFunction(controlPoints: 0.39, 0.575, 0.565, 1.0)
let progress: CGFloat = MaxStrokeLength
let endFromValue: CGFloat = circleShapeLayer.strokeEnd
let endToValue: CGFloat = endFromValue + progress
let strokeEnd = CABasicAnimation(keyPath: "strokeEnd")
strokeEnd.fromValue = endFromValue
strokeEnd.toValue = endToValue
strokeEnd.duration = 0.5
strokeEnd.fillMode = kCAFillModeForwards
strokeEnd.timingFunction = easeInOutSineTimingFunc
strokeEnd.beginTime = 0.1
strokeEnd.isRemovedOnCompletion = false
let startFromValue: CGFloat = circleShapeLayer.strokeStart
let startToValue: CGFloat = fabs(endToValue - MinStrokeLength)
let strokeStart = CABasicAnimation(keyPath: "strokeStart")
strokeStart.fromValue = startFromValue
strokeStart.toValue = startToValue
strokeStart.duration = 0.4
strokeStart.fillMode = kCAFillModeForwards
strokeStart.timingFunction = easeInOutSineTimingFunc
strokeStart.beginTime = strokeEnd.beginTime + strokeEnd.duration + 0.2
strokeStart.isRemovedOnCompletion = false
let pathAnim = CAAnimationGroup()
pathAnim.animations = [strokeEnd, strokeStart]
pathAnim.duration = strokeStart.beginTime + strokeStart.duration
pathAnim.fillMode = kCAFillModeForwards
pathAnim.isRemovedOnCompletion = false
CATransaction.begin()
CATransaction.setCompletionBlock {
if self.circleShapeLayer.animation(forKey: "stroke") != nil {
self.circleShapeLayer.transform = CATransform3DRotate(self.circleShapeLayer.transform, CGFloat(M_PI*2) * progress, 0, 0, 1)
self.circleShapeLayer.removeAnimation(forKey: "stroke")
self.startStrokeAnimation()
}
}
circleShapeLayer.add(pathAnim, forKey: "stroke")
CATransaction.commit()
}
func stopAnimating() {
circleShapeLayer.removeAllAnimations()
layer.removeAllAnimations()
circleShapeLayer.transform = CATransform3DIdentity
layer.transform = CATransform3DIdentity
}
}
extension UIColor {
convenience init(hex: UInt, alpha: CGFloat) {
self.init(
red: CGFloat((hex & 0xFF0000) >> 16) / 255.0,
green: CGFloat((hex & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(hex & 0x0000FF) / 255.0,
alpha: CGFloat(alpha)
)
}
}
And here is the code of my view controller in the viewdidload
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568))
let indicator = MaterialLoadingIndicator(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
indicator.center = CGPoint(x: 320*0.5, y: 568*0.5)
view.addSubview(indicator)
indicator.startAnimating()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The view holding the indicator is just floating around, feeling lost, feeling unhappy for not belonging to, not being added to someone. :)
EDIT :
Okay John, now that we are stuck, let us add the indicator to someone.
override func viewDidLoad() {
super.viewDidLoad()
let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568))
let indicator = MaterialLoadingIndicator(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
indicator.center = CGPoint(x: 320*0.5, y: 568*0.5)
view.addSubview(indicator)
indicator.startAnimating()
self.view.addSubview(view) // John, this is what was missing
}

Resources