How can I add a little white tick to SSRadioButton - swift

The following is my code. I want to add a little white tick to middle when i clicked the button. How can i do that by programming but not using image...
import Foundation
import UIKit
#IBDesignable
class SSRadioButton: UIButton {
private var circleLayer = CAShapeLayer()
private var fillCircleLayer = CAShapeLayer()
override var selected: Bool {
didSet {
toggleButon()
}
}
/**
Color of the radio button circle. Default value is UIColor red.
*/
#IBInspectable var circleColor: UIColor = UIColor.redColor() {
didSet {
circleLayer.strokeColor = circleColor.CGColor
self.toggleButon()
}
}
/**
Radius of RadioButton circle.
*/
#IBInspectable var circleRadius: CGFloat = 5.0
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
private func circleFrame() -> CGRect {
var circleFrame = CGRect(x: 0, y: 0, width: 2*circleRadius, height: 2*circleRadius)
circleFrame.origin.x = 0 + circleLayer.lineWidth
circleFrame.origin.y = bounds.height/2 - circleFrame.height/2
return circleFrame
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
private func initialize() {
circleLayer.frame = bounds
circleLayer.lineWidth = 2
circleLayer.fillColor = UIColor.clearColor().CGColor
circleLayer.strokeColor = circleColor.CGColor
layer.addSublayer(circleLayer)
fillCircleLayer.frame = bounds
fillCircleLayer.lineWidth = 2
fillCircleLayer.fillColor = UIColor.clearColor().CGColor
fillCircleLayer.strokeColor = UIColor.clearColor().CGColor
layer.addSublayer(fillCircleLayer)
self.titleEdgeInsets = UIEdgeInsetsMake(0, (4*circleRadius + 4*circleLayer.lineWidth), 0, 0)
self.toggleButon()
}
/**
Toggles selected state of the button.
*/
func toggleButon() {
if self.selected {
fillCircleLayer.fillColor = circleColor.CGColor
} else {
fillCircleLayer.fillColor = UIColor.clearColor().CGColor
}
}
private func circlePath() -> UIBezierPath {
return UIBezierPath(ovalInRect: circleFrame())
}
private func fillCirclePath() -> UIBezierPath {
return UIBezierPath(ovalInRect: CGRectInset(circleFrame(), 2, 2))
}
override func layoutSubviews() {
super.layoutSubviews()
circleLayer.frame = bounds
circleLayer.path = circlePath().CGPath
fillCircleLayer.frame = bounds
fillCircleLayer.path = fillCirclePath().CGPath
self.titleEdgeInsets = UIEdgeInsetsMake(0, (2*circleRadius + 4*circleLayer.lineWidth), 0, 0)
}
override func prepareForInterfaceBuilder() {
initialize()
}
}

Something like this?
You could do achieve by using UIBezierPath to draw a couple of lines that make a tick. Unless you were looking for something more fancy or curvy? The answer to this question: Draw a line with UIBezierPath
has a nice little function that simplifies the process of drawing the lines.

Related

Adding confetti animation behind the human/Animal body to a Video

I want to add confetti animation to a video so that when any animal/human appears then confetti animation should be running behind the human/animal's body. I used following code but here confetti is running as overlay in video.
import UIKit
import QuartzCore
class ConfettiView: UIView {
var emitter: CAEmitterLayer!
var colors: [UIColor]!
var intensity: Float!
private var active : Bool = false
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
colors = [UIColor.red,
UIColor.green,
UIColor.blue
]
intensity = 0.2
active = false
}
func startConfetti() {
emitter = CAEmitterLayer()
emitter.emitterPosition = CGPoint(x: frame.size.width / 2.0, y: -20)
emitter.emitterShape = CAEmitterLayerEmitterShape.line
emitter.emitterSize = CGSize(width: frame.size.width, height: 1)
var cells = [CAEmitterCell]()
for color in colors {
cells.append(confettiWithColor(color: color))
}
emitter.emitterCells = cells
layer.addSublayer(emitter)
active = true
}
func stopConfetti() {
emitter?.birthRate = 0
active = false
}
func confettiWithColor(color: UIColor) -> CAEmitterCell {
let confetti = CAEmitterCell()
confetti.birthRate = 10.0 * intensity
confetti.lifetime = 180.0 * intensity
confetti.lifetimeRange = 0
confetti.color = color.cgColor
confetti.velocity = CGFloat(350.0 * intensity)
confetti.velocityRange = CGFloat(40.0 * intensity)
confetti.emissionLongitude = CGFloat(Double.pi)
confetti.emissionRange = 0.4
confetti.spin = CGFloat(3.5 * intensity)
confetti.spinRange = 0.0
confetti.scale = 0.1
confetti.contents = UIImage(named: "image")?.cgImage
return confetti
}
}
// then I add this confettiView as a subView of VidePlayerView
I need to show/export video with confetti like this.
Watch it. Here confetti is running behind the woman.

Swift: Make the outline of a UIView sketch-like

I want to make the outlines of a UIView look "wavey" like someone drew them.
I have this example from PowerPoint, which allows to do it (should work with any size and corner radius):
Currently this is what I have:
myView.layer.borderWidth = 10
myView.layer.borderColor = UIColor.blue.cgColor
myView.layer.cornerRadius = 5 // Optional
Thank
You can create "wavy" lines by using a UIBezierPath with a combination of quad-curves, lines, arcs, etc.
We'll start with a simple line, one-quarter of the width of the view:
Our path would consist of:
move to 0,0
add line to 80,0
If we change that to a quad-curve:
Now we're doing:
move to 0,0
add quad-curve to 80,0 with control point 40,40
If we add another quad-curve going the other way:
Now we're doing:
move to 0,0
add quad-curve to 80,0 with control point 40,40
add quad-curve to 160,0 with control point 120,-40
and we can extend that the width of the view:
of course, that doesn't look like your "sketch" target, so let's change the control-point offsets from 40 to 2:
Now it looks a bit more like a hand-draw "sketched" line.
It's too uniform, though, and it's partially outside the bounds of the view, so let's inset it by 8-pts and, instead of four 25% segments, we'll use (for example) five segments of these widths:
0.15, 0.2, 0.2, 0.27, 0.18
If we take the same approach to go down the right-hand side, back across the bottom, and up the left-hand side, we can get this:
Here's some example code to produce that view:
class SketchBorderView: UIView {
let borderLayer: CAShapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.blue.cgColor
layer.addSublayer(borderLayer)
backgroundColor = .yellow
}
override func layoutSubviews() {
let incrementVals: [CGFloat] = [
0.15, 0.2, 0.2, 0.27, 0.18,
]
let lineOffsets: [[CGFloat]] = [
[ 1.0, -2.0],
[-1.0, 2.0],
[-1.0, -2.0],
[ 1.0, 2.0],
[ 0.0, -2.0],
]
let pth: UIBezierPath = UIBezierPath()
// inset bounds by 8-pts so we can draw the "wavy border"
// inside our bounds
let r: CGRect = bounds.insetBy(dx: 8.0, dy: 8.0)
var ptDest: CGPoint = .zero
var ptControl: CGPoint = .zero
// start at top-left
ptDest = r.origin
pth.move(to: ptDest)
// we're at top-left
for i in 0..<incrementVals.count {
ptDest.x += r.width * incrementVals[i]
ptDest.y = r.minY + lineOffsets[i][0]
ptControl.x = pth.currentPoint.x + ((ptDest.x - pth.currentPoint.x) * 0.5)
ptControl.y = r.minY + lineOffsets[i][1]
pth.addQuadCurve(to: ptDest, controlPoint: ptControl)
}
// now we're at top-right
for i in 0..<incrementVals.count {
ptDest.y += r.height * incrementVals[i]
ptDest.x = r.maxX + lineOffsets[i][0]
ptControl.y = pth.currentPoint.y + ((ptDest.y - pth.currentPoint.y) * 0.5)
ptControl.x = r.maxX + lineOffsets[i][1]
pth.addQuadCurve(to: ptDest, controlPoint: ptControl)
}
// now we're at bottom-right
for i in 0..<incrementVals.count {
ptDest.x -= r.width * incrementVals[i]
ptDest.y = r.maxY + lineOffsets[i][0]
ptControl.x = pth.currentPoint.x - ((pth.currentPoint.x - ptDest.x) * 0.5)
ptControl.y = r.maxY + lineOffsets[i][1]
pth.addQuadCurve(to: ptDest, controlPoint: ptControl)
}
// now we're at bottom-left
for i in 0..<incrementVals.count {
ptDest.y -= r.height * incrementVals[i]
ptDest.x = r.minX + lineOffsets[i][0]
ptControl.y = pth.currentPoint.y - ((pth.currentPoint.y - ptDest.y) * 0.5)
ptControl.x = r.minX + lineOffsets[i][1]
pth.addQuadCurve(to: ptDest, controlPoint: ptControl)
}
borderLayer.path = pth.cgPath
}
}
and an example controller:
class SketchTestVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = SketchBorderView()
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
v.centerXAnchor.constraint(equalTo: g.centerXAnchor),
v.centerYAnchor.constraint(equalTo: g.centerYAnchor),
v.widthAnchor.constraint(equalToConstant: 320.0),
v.heightAnchor.constraint(equalTo: v.widthAnchor),
])
}
}
Using that code, though, we still have too much uniformity, so in actual use we'd want to randomize the number of segments, the widths of the segments, and the control-point offsets.
Of course, to get your "rounded rect" you'd want to add arcs at the corners.
I expect this should get you on your way though.
use this extension to solve the issue
import Foundation
import UIKit
extension UIView {
func dropShadow(scale: Bool = true) {
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.2
layer.shadowOffset = .zero
layer.shadowRadius = 5
layer.shouldRasterize = true
layer.rasterizationScale = scale ? UIScreen.main.scale : 1
}
#IBInspectable
var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
#IBInspectable
var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set {
layer.borderWidth = newValue
}
}
#IBInspectable
var borderColor: UIColor? {
get {
let color = UIColor.init(cgColor: layer.borderColor!) //UIColor.init(CGColor: layer.borderColor!)
return color
}
set {
layer.borderColor = newValue?.cgColor
}
}
#IBInspectable
var shadowRadius: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue
}
}
#IBInspectable
var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
#IBInspectable
var shadowOffset: CGSize {
get {
return layer.shadowOffset
}
set {
layer.shadowOffset = newValue
}
}
#IBInspectable
var shadowColor: UIColor? {
get {
if let color = layer.shadowColor {
return UIColor(cgColor: color)
}
return nil
}
set {
if let color = newValue {
layer.shadowColor = color.cgColor
} else {
layer.shadowColor = nil
}
}
}
}
it will look like this

How can I centre a circular progress bar in a UIView using Swift?

I'm having quite a bit of difficulty simply ensuring a circular progress bar is always in the centre of the UIView it is associated to.
This is what is happening:
Ignore the grey region, this is simply the UIView on a placeholder card. The red is the UIView I have added as an outlet to the UIViewController.
Below is the code for the class that I have made:
class CircleProgress: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
var progressLyr = CAShapeLayer()
var trackLyr = CAShapeLayer()
var progressClr = UIColor.red {
didSet {
progressLyr.strokeColor = progressClr.cgColor
}
}
var trackClr = UIColor.black {
didSet {
trackLyr.strokeColor = trackClr.cgColor
}
}
private func setupView() {
self.backgroundColor = UIColor.red
let centre = CGPoint(x: frame.size.width/2, y: frame.size.height/2)
let circlePath = UIBezierPath(arcCenter: centre,
radius: 10,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: true)
trackLyr.path = circlePath.cgPath
trackLyr.fillColor = UIColor.clear.cgColor
trackLyr.strokeColor = trackClr.cgColor
trackLyr.lineWidth = 5.0
trackLyr.strokeEnd = 1.0
layer.addSublayer(trackLyr)
}
}
The aim is simply to have all 4 edges of the black circle touching the edges of the red square.
Any help is hugely appreciated. I'm thinking it must be too obvious but this has cost me too many hours tonight. :)
The problem is that there is no frame being passed when creating the object. No need for changing anything to your code. For sure you have to change the width and the height to whatever you want.
Here is an example...
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
// Add circleProgress
addCircleProgress()
}
private func addCircleProgress() {
let circleProgress = CircleProgress(frame: CGRect(x: self.view.center.x - 50, y: self.view.center.x - 50, width: 100, height: 100))
self.view.addSubview(circleProgress)
}
}
class CircleProgress: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var progressLyr = CAShapeLayer()
var trackLyr = CAShapeLayer()
var progressClr = UIColor.red {
didSet {
progressLyr.strokeColor = progressClr.cgColor
}
}
var trackClr = UIColor.black {
didSet {
trackLyr.strokeColor = trackClr.cgColor
}
}
private func setupView() {
self.backgroundColor = UIColor.red
let centre = CGPoint(x: frame.size.width/2, y: frame.size.height/2)
let circlePath = UIBezierPath(arcCenter: centre,
radius: 50,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: true)
trackLyr.path = circlePath.cgPath
trackLyr.fillColor = UIColor.clear.cgColor
trackLyr.strokeColor = trackClr.cgColor
trackLyr.lineWidth = 5.0
trackLyr.strokeEnd = 1.0
layer.addSublayer(trackLyr)
}
}
A few suggestions:
I’d suggest making this CircularProgress take up the whole view. If you want this to be inset within the view, then, fine, add a property for that. In my example below, I created a property called inset to capture this value.
Make sure to update your path in layoutSubviews. Especially if you use constraints, you want this to respond to size changes. So add these layers from init, but update the path in layoutSubviews.
Don’t reference frame (which is the location within the superview coordinate system). Use bounds. And inset it by half the line width, so the circle doesn’t exceed the bounds of the view.
You created a progress color and progress layer, but didn’t use either one. I’ve guessed you wanted that to show the progress within the track.
You are stroking your path from 0 to 2π. People tend to expect these circular progress views to start from -π/2 (12 o’clock) and progress to 3π/2. So I’ve updated the path to use those values. But use whatever you want.
If you want, you can make it #IBDesignable if you want to see this rendered in IB.
Thus, pulling that together, you get:
#IBDesignable
public class CircleProgress: UIView {
#IBInspectable
public var lineWidth: CGFloat = 5 { didSet { updatePath() } }
#IBInspectable
public var strokeEnd: CGFloat = 1 { didSet { progressLayer.strokeEnd = strokeEnd } }
#IBInspectable
public var trackColor: UIColor = .black { didSet { trackLayer.strokeColor = trackColor.cgColor } }
#IBInspectable
public var progressColor: UIColor = .red { didSet { progressLayer.strokeColor = progressColor.cgColor } }
#IBInspectable
public var inset: CGFloat = 0 { didSet { updatePath() } }
private lazy var trackLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.fillColor = UIColor.clear.cgColor
layer.strokeColor = trackColor.cgColor
layer.lineWidth = lineWidth
return layer
}()
private lazy var progressLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.fillColor = UIColor.clear.cgColor
layer.strokeColor = progressColor.cgColor
layer.lineWidth = lineWidth
return layer
}()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
public override func layoutSubviews() {
super.layoutSubviews()
updatePath()
}
}
private extension CircleProgress {
func setupView() {
layer.addSublayer(trackLayer)
layer.addSublayer(progressLayer)
}
func updatePath() {
let rect = bounds.insetBy(dx: lineWidth / 2 + inset, dy: lineWidth / 2 + inset)
let centre = CGPoint(x: rect.midX, y: rect.midY)
let radius = min(rect.width, rect.height) / 2
let path = UIBezierPath(arcCenter: centre,
radius: radius,
startAngle: -.pi / 2,
endAngle: 3 * .pi / 2,
clockwise: true)
trackLayer.path = path.cgPath
trackLayer.lineWidth = lineWidth
progressLayer.path = path.cgPath
progressLayer.lineWidth = lineWidth
}
}
That yields:
Update this function
private func setupView() {
self.backgroundColor = UIColor.red
let centre = CGPoint(x: bounds.midX, y: bounds.midY)
let circlePath = UIBezierPath(arcCenter: centre,
radius: bounds.maxX/2,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: true)
trackLyr.path = circlePath.cgPath
trackLyr.fillColor = UIColor.clear.cgColor
trackLyr.strokeColor = trackClr.cgColor
trackLyr.lineWidth = 5.0
trackLyr.strokeEnd = 1.0
layer.addSublayer(trackLyr)
}

How do I Create my own Progressbar in swift? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I to create my own custom progressbar ie:NSProgressIndicator using swift how do I do that?
You can just make your own progress indicator, e.g.:
#IBDesignable
open class ColorfulProgressIndicator: NSView {
#IBInspectable open var doubleValue: Double = 50 { didSet { needsLayout = true } }
#IBInspectable open var minValue: Double = 0 { didSet { needsLayout = true } }
#IBInspectable open var maxValue: Double = 100 { didSet { needsLayout = true } }
#IBInspectable open var backgroundColor: NSColor = .lightGray { didSet { layer?.backgroundColor = backgroundColor.cgColor } }
#IBInspectable open var progressColor: NSColor = .blue { didSet { progressShapeLayer.fillColor = progressColor.cgColor } }
#IBInspectable open var borderColor: NSColor = .clear { didSet { layer?.borderColor = borderColor.cgColor } }
#IBInspectable open var borderWidth: CGFloat = 0 { didSet { layer?.borderWidth = borderWidth } }
#IBInspectable open var cornerRadius: CGFloat = 0 { didSet { layer?.cornerRadius = cornerRadius } }
private lazy var progressShapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = progressColor.cgColor
return shapeLayer
}()
public override init(frame: NSRect = .zero) {
super.init(frame: frame)
configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
// needed because IB doesn't don't honor `wantsLayer`
open override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer = CALayer()
configure()
}
open override func layout() {
super.layout()
updateProgress()
}
open func animate(to doubleValue: Double? = nil, minValue: Double? = nil, maxValue: Double? = nil, duration: TimeInterval = 0.25) {
let currentPath = progressShapeLayer.presentation()?.path ?? progressShapeLayer.path
// stop prior animation, if any
progressShapeLayer.removeAnimation(forKey: "updatePath")
// update progress properties
if let doubleValue = doubleValue { self.doubleValue = doubleValue }
if let minValue = minValue { self.minValue = minValue }
if let maxValue = maxValue { self.maxValue = maxValue }
// create new animation
let animation = CABasicAnimation(keyPath: "path")
animation.duration = duration
animation.fromValue = currentPath
animation.toValue = progressPath
progressShapeLayer.add(animation, forKey: "updatePath")
}
}
private extension ColorfulProgressIndicator {
func configure() {
wantsLayer = true
layer?.cornerRadius = cornerRadius
layer?.backgroundColor = backgroundColor.cgColor
layer?.borderWidth = borderWidth
layer?.borderColor = borderColor.cgColor
layer?.addSublayer(progressShapeLayer)
}
func updateProgress() {
progressShapeLayer.path = progressPath
}
var progressPath: CGPath? {
guard minValue != maxValue else { return nil }
let percent = max(0, min(1, CGFloat((doubleValue - minValue) / (maxValue - minValue))))
let rect = NSRect(origin: bounds.origin, size: CGSize(width: bounds.width * percent, height: bounds.height))
return CGPath(rect: rect, transform: nil)
}
}
You can either just set its doubleValue, minValue and maxValue, or if you want to animate the change, just:
progressIndicator.animate(to: 75)
For example, below I set the progressColor and borderColor to .red, set the borderWidth to 1, set the cornerRadius to 10. I then started animating to 75, and then, before it’s even done, triggered another animation to 100 (to illustrate that animations can pick up from wherever it left off):
There are tons of ways of implementing this (so get too lost in the details of the implementation, above), but it illustrates that creating our own progress indicators is pretty easy.
You can subclass it just like any other view. But for all you're doing that is likely unnecessary.
class CustomIndicator: NSProgressIndicator {
// ...
}
As far as setting the height goes, you can do this by initializing the view with a custom frame.
let indicator = NSProgressIndicator(frame: NSRect(x: 0, y: 0, width: 100, height: 20))
There is a property called controlTint that you can set on NSProgressIndicator, but this only allows you to set the color to the predefined ones under NSControlTint. For truly custom colors, I'd recommend this route using Quartz filters via the Interface Builder.

How to make a simple UILabel subclass for marquee/scrolling text effect in swift?

As you can see above i trying to code an simple(!) subclass of UILabel to make an marquee or scrolling text effect if the text of the label is too long. I know that there are already good classes out there (e.g https://cocoapods.org/pods/MarqueeLabel), but i want to make my own :)
Down below you can see my current class.
I can't also fix an issue where the new label(s) are scrolling right, but there is also a third label which shouldn't be there. I think it's the label itself. But when i try the replace the first additional label with that label i won't work. I hope it's not too confusing :/
It's important to me that i only have to assign the class in the storyboard to the label. So that there is no need go and add code e.g in an view controller (beside the outlets). I hope it's clear what i want :D
So again:
Simple subclass of UILabel
scrolling label
should work without any additional code in other classes (except of outlets to change the labels text for example,...)
(it's my first own subclass, so feel free to teach me how to do it right :) )
Thank you very much !
It's by far not perfect, but this is my current class:
import UIKit
class LoopLabel: UILabel {
var labelText : String?
var rect0: CGRect!
var rect1: CGRect!
var labelArray = [UILabel]()
var isStop = false
var timeInterval: TimeInterval!
let leadingBuffer = CGFloat(25.0)
let loopStartDelay = 2.0
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.lineBreakMode = .byClipping
}
override var text: String? {
didSet {
labelText = text
setup()
}
}
func setup() {
let label = UILabel()
label.frame = CGRect.zero
label.text = labelText
timeInterval = TimeInterval((labelText?.characters.count)! / 5)
let sizeOfText = label.sizeThatFits(CGSize.zero)
let textIsTooLong = sizeOfText.width > frame.size.width ? true : false
rect0 = CGRect(x: leadingBuffer, y: 0, width: sizeOfText.width, height: self.bounds.size.height)
rect1 = CGRect(x: rect0.origin.x + rect0.size.width, y: 0, width: sizeOfText.width, height: self.bounds.size.height)
label.frame = rect0
super.clipsToBounds = true
labelArray.append(label)
self.addSubview(label)
self.frame = CGRect(origin: self.frame.origin, size: CGSize(width: 0, height: 0))
if textIsTooLong {
let additionalLabel = UILabel(frame: rect1)
additionalLabel.text = labelText
self.addSubview(additionalLabel)
labelArray.append(additionalLabel)
animateLabelText()
}
}
func animateLabelText() {
if(!isStop) {
let labelAtIndex0 = labelArray[0]
let labelAtIndex1 = labelArray[1]
UIView.animate(withDuration: timeInterval, delay: loopStartDelay, options: [.curveLinear], animations: {
labelAtIndex0.frame = CGRect(x: -self.rect0.size.width,y: 0,width: self.rect0.size.width,height: self.rect0.size.height)
labelAtIndex1.frame = CGRect(x: labelAtIndex0.frame.origin.x + labelAtIndex0.frame.size.width,y: 0,width: labelAtIndex1.frame.size.width,height: labelAtIndex1.frame.size.height)
}, completion: { finishied in
labelAtIndex0.frame = self.rect1
labelAtIndex1.frame = self.rect0
self.labelArray[0] = labelAtIndex1
self.labelArray[1] = labelAtIndex0
self.animateLabelText()
})
} else {
self.layer.removeAllAnimations()
}
}
}
First of, I would keep the variables private if you don't need them to be accessed externally, especially labelText (since you're using the computed property text to be set).
Second, since you're adding labels as subviews, I'd rather use a UIView as container instead of UILabel. The only difference in the storyboard would be to add a View instead of a Label.
Third, if you use this approach, you should not set the frame (of the view) to zero.
Something like that would do:
import UIKit
class LoopLabelView: UIView {
private var labelText : String?
private var rect0: CGRect!
private var rect1: CGRect!
private var labelArray = [UILabel]()
private var isStop = false
private var timeInterval: TimeInterval!
private let leadingBuffer = CGFloat(25.0)
private let loopStartDelay = 2.0
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var text: String? {
didSet {
labelText = text
setup()
}
}
func setup() {
self.backgroundColor = UIColor.yellow
let label = UILabel()
label.text = labelText
label.frame = CGRect.zero
timeInterval = TimeInterval((labelText?.characters.count)! / 5)
let sizeOfText = label.sizeThatFits(CGSize.zero)
let textIsTooLong = sizeOfText.width > frame.size.width ? true : false
rect0 = CGRect(x: leadingBuffer, y: 0, width: sizeOfText.width, height: self.bounds.size.height)
rect1 = CGRect(x: rect0.origin.x + rect0.size.width, y: 0, width: sizeOfText.width, height: self.bounds.size.height)
label.frame = rect0
super.clipsToBounds = true
labelArray.append(label)
self.addSubview(label)
//self.frame = CGRect(origin: self.frame.origin, size: CGSize(width: 0, height: 0))
if textIsTooLong {
let additionalLabel = UILabel(frame: rect1)
additionalLabel.text = labelText
self.addSubview(additionalLabel)
labelArray.append(additionalLabel)
animateLabelText()
}
}
func animateLabelText() {
if(!isStop) {
let labelAtIndex0 = labelArray[0]
let labelAtIndex1 = labelArray[1]
UIView.animate(withDuration: timeInterval, delay: loopStartDelay, options: [.curveLinear], animations: {
labelAtIndex0.frame = CGRect(x: -self.rect0.size.width,y: 0,width: self.rect0.size.width,height: self.rect0.size.height)
labelAtIndex1.frame = CGRect(x: labelAtIndex0.frame.origin.x + labelAtIndex0.frame.size.width,y: 0,width: labelAtIndex1.frame.size.width,height: labelAtIndex1.frame.size.height)
}, completion: { finishied in
labelAtIndex0.frame = self.rect1
labelAtIndex1.frame = self.rect0
self.labelArray[0] = labelAtIndex1
self.labelArray[1] = labelAtIndex0
self.animateLabelText()
})
} else {
self.layer.removeAllAnimations()
}
}
}