Я пытаюсь создать анимацию индикатора выполнения в Swift. Я разработал шаблонное изображение для имитации прогресса, но не могу напрямую применить эту анимацию к UIProgressView. Вместо этого я поместил UIView сверху, чтобы имитировать это поведение.
Я сделал симметричную картинку: зеленый фон, темно-зеленые горизонтальные линии.
Как я понял, поскольку я не могу прикрепить его к UIProgressBar, я добавил UIView для имитации ProgressBar.
Идея состоит в том, чтобы иметь что-то вроде этого:
Вот моя текущая настройка:
import UIKit
class SideMenuButtons: UIViewController {
var progressOverlayView: UIView!
@IBOutlet weak var energyReloadingIndicator: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
progressOverlayView = UIView()
if let patternImage = UIImage(named: "striped-pattern-repeated") {
progressOverlayView.backgroundColor = UIColor(patternImage: patternImage)
} else {
progressOverlayView.backgroundColor = .red
}
energyReloadingIndicator.addSubview(progressOverlayView)
updateProgressOverlayViewWidth()
}
private func updateProgressOverlayViewWidth() {
let progress = CGFloat(energyReloadingIndicator.progress)
let maxWidth = energyReloadingIndicator.frame.width
let overlayWidth = maxWidth * progress
let overlayHeight = energyReloadingIndicator.frame.height
progressOverlayView.frame = CGRect(x: 0, y: 0, width: overlayWidth, height: overlayHeight)
}
}
Как я могу анимировать шаблон в ProgressOverlayView, чтобы добиться эффекта непрерывного движения?
Вы можете скачать gif-изображение здесь icon8.com/preloaders/en/horizontal и загрузить gif-изображение в свой код.





По сути, мы можем добавить изображение gif в представление изображения и добиться анимации.
1. Нравится этот ответ GIF-изображение для добавления
let jeremyGif = UIImage.gifImageWithName("loader")
loaderImageView = UIImageView(image: jeremyGif)
loaderImageView?.contentMode = .left
loaderImageView?.clipsToBounds = true
Как вы сказали, возьмите таймер
2. timerForIncreaseProgress = Timer.scheduledTimer(timeInterval: 1, цель: self, селектор: #selector(increaseProgress), userInfo: ноль, повторы: true)
@objc func increaseProgress() {
progress = progress + 0.05
progressView.progress = progress
let maxWidth = progressView.frame.width
let overlayWidth = Float(maxWidth) * progress
loaderImageView?.frame = CGRect(x: 0, y: 0, width: Double(overlayWidth), height: 13.0)
print("progressOverlayView.width \(progressOverlayView.frame.width)")
if progress >= 1 {
timerForIncreaseProgress?.invalidate()
timerForIncreaseProgress = nil
}
}
Надеюсь, это поможет вам
Давайте рассмотрим один подход, который не требует анимированного GIF-изображения или изображения-шаблона.
Мы можем использовать UIBezierPath с CAShapeLayer для создания шаблона в подклассе пользовательского представления:
и мы можем установить цвет фона представления с помощью цвета «между строк»:
Теперь мы можем использовать CABasicAnimation(keyPath: "position.x"), чтобы «сдвинуть» этот слой влево. Мы создаем путь линий шире, чем вид, чтобы не видеть пробела справа (не обращайте внимания на «сбой» в этой анимации, он присутствует только потому, что это частичный захват):
Исходное изображение также имеет небольшой градиент, поэтому мы можем наложить полупрозрачный градиент для этого вида:
Теперь мы можем создать собственное представление «прогресса», встроив это представление анимированной фигуры в представление «контейнера» и отрегулировав ширину контейнера, чтобы отразить прогресс:
Вот пример кода...
AnimatedPatternView: все свойства размера, цвета, скорости и т. д. расположены вверху, что позволяет легко настроить их по своему вкусу:
class AnimatedPatternView: UIView {
private let angledLinesShapeLayer = CAShapeLayer()
private let overlayGradLayer = CAGradientLayer()
// let's put all of our appearance variables here
// in one place, to make it easier to adjust them
// this will be the amount of time (in seconds) that the
// animation takes to slide "one tile" to the left
private let animSpeed: Double = 0.5
// "tile" width
private let tileWidth: CGFloat = 40.0
// we want an angled line, not the full width
private let angledLineBottomOffset: CGFloat = 32.0
// thickness of the lines
private let angledLineThickness: CGFloat = 14.0
// the angled-line and "space between" colors
private let angledLineColor: UIColor = UIColor(red: 0.557, green: 0.420, blue: 0.863, alpha: 1.0)
private let spaceBetweenColor: UIColor = UIColor(red: 0.624, green: 0.494, blue: 0.886, alpha: 1.0)
// soft overlay gradient
private let overlayGradientColors: [CGColor] = [
UIColor.black.withAlphaComponent(0.2).cgColor,
UIColor.white.withAlphaComponent(0.1).cgColor,
UIColor.black.withAlphaComponent(0.05).cgColor,
]
// gradient angle
private let gradStartPoint: CGPoint = .init(x: 0.250, y: 0.0)
private let gradEndPoint: CGPoint = .init(x: 0.750, y: 1.0)
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
self.backgroundColor = spaceBetweenColor
angledLinesShapeLayer.strokeColor = angledLineColor.cgColor
angledLinesShapeLayer.lineWidth = angledLineThickness
angledLinesShapeLayer.lineCap = .square
overlayGradLayer.colors = overlayGradientColors
overlayGradLayer.startPoint = gradStartPoint
overlayGradLayer.endPoint = gradEndPoint
self.layer.addSublayer(angledLinesShapeLayer)
self.layer.addSublayer(overlayGradLayer)
self.layer.masksToBounds = true
self.clipsToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
let bez = UIBezierPath()
var x: CGFloat = 0.0
while x < bounds.width + tileWidth {
bez.move(to: .init(x: x, y: bounds.minY))
bez.addLine(to: .init(x: x + angledLineBottomOffset, y: bounds.maxY))
x += tileWidth
}
angledLinesShapeLayer.path = bez.cgPath
// gradient layer needs to match the view bounds
overlayGradLayer.frame = bounds
angledLinesShapeLayer.removeAllAnimations()
let animation = CABasicAnimation(keyPath: "position.x")
animation.fromValue = 0.0
animation.toValue = -tileWidth
animation.duration = animSpeed
animation.repeatCount = .infinity
angledLinesShapeLayer.add(animation, forKey: "stripeAnim")
}
}
EnergyProgressView: подкласс представления, который использует AnimatedPatternView и представление контейнера для имитации индикатора выполнения:
class EnergyProgressView: UIView {
public var progress: Float {
set { _progress = newValue }
get { return _progress }
}
private var _progress: Float = 1.0 {
didSet {
wConstraint.isActive = false
wConstraint = containerView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: CGFloat(_progress))
wConstraint.isActive = true
}
}
private let animPatternView = AnimatedPatternView()
private let containerView = UIView()
// this will control the width of the container
// using progress as a percentage of the width
private var wConstraint: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
animPatternView.translatesAutoresizingMaskIntoConstraints = false
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(animPatternView)
self.addSubview(containerView)
containerView.clipsToBounds = true
// we'll be modifying the width of containerView to reflect the progress
wConstraint = containerView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1.0)
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0.0),
containerView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0.0),
containerView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0.0),
wConstraint,
// even though animPatternView is a subview of containerView
// we constrain animPatternView to self so it doesn't resize
// when the progress changes
animPatternView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0.0),
animPatternView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0.0),
animPatternView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0.0),
animPatternView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0.0),
])
self.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
self.layer.masksToBounds = true
self.layer.cornerRadius = 8.0
}
func setProgress(_ p: Float, animated: Bool = false) {
_progress = p
if animated {
UIView.animate(withDuration: 0.3, animations: {
self.layoutIfNeeded()
})
}
}
}
ViewController: пример контроллера представления с пользовательским индикатором выполнения и ползунком для обновления прогресса:
class ViewController: UIViewController {
let myProgressView = EnergyProgressView()
// let's add a label and a slider so we can dynamically set the progress
let pctLabel = UILabel()
let slider = UISlider()
override func viewDidLoad() {
super.viewDidLoad()
myProgressView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myProgressView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
myProgressView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
myProgressView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
myProgressView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
myProgressView.heightAnchor.constraint(equalToConstant: 32.0),
])
pctLabel.textAlignment = .center
pctLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pctLabel)
slider.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(slider)
NSLayoutConstraint.activate([
pctLabel.topAnchor.constraint(equalTo: myProgressView.bottomAnchor, constant: 40.0),
pctLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
pctLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
slider.topAnchor.constraint(equalTo: pctLabel.bottomAnchor, constant: 40.0),
slider.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
slider.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
slider.addTarget(self, action: #selector(handleSlider(_:)), for: .valueChanged)
slider.value = 0.5
myProgressView.progress = 0.5
updatePctLabel()
}
@objc func handleSlider(_ sender: UISlider) {
myProgressView.progress = sender.value
updatePctLabel()
}
func updatePctLabel() {
let v = myProgressView.progress
pctLabel.text = String(format: "%0.2f %%", v * 100.0)
}
}
Обратите внимание: это только пример кода!!! Его следует использовать в качестве инструмента обучения и не следует считать «готовым к производству».
Дорогой @DonMag, если бы я мог тебя обнять или послать цветы - я бы обязательно это сделал :D Большое-большое спасибо!
Одним из решений может быть необходимость сделать 3 изображения индикатора выполнения, например, непрерывно. Нужно взять таймер 0,2 секунды и менять изображение после каждого интервала. Если поместить его в блок UIViewAnimation, вы достигнете желаемого.