我正在try 在Swift中创建进度条动画.我设计了一个图案化的图像来模拟进度,但无法直接将此动画应用到UI ProgressView.相反,我在上面放置了UI View来模仿这种行为.

我做了一张对称的照片:绿色背景,深绿色水平线.

据我所知,由于无法将其附加到UI ProgressBar上,因此我添加了UI View来模拟ProgressBar.

The idea is to have something like this: loading bar

这是我当前的设置:

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)
    }
}

如何对正在进行的图案进行动画化OverlayView以实现连续移动效果?

推荐答案

让我们看看一种不需要动画gif、or个图案图像的方法.

我们可以使用UIBezierPathCAShapeLayer来在自定义视图子集中创建模式:

enter image description here

我们可以使用"字里行间" colored颜色 来设置视图的背景色:

enter image description here

现在我们可以使用CABasicAnimation(keyPath: "position.x")将该层向左"滑动".我们将线路径创建得比视图更宽,这样我们就不会在右侧看到间隙(忽略此动画中的"打嗝",它之所以存在,只是因为它是部分捕获):

enter image description here

您的原始图像似乎也有轻微的渐变,因此我们可以为该外观叠加半透明渐变:

enter image description here

现在,我们可以通过将动画形状视图嵌入到"容器"视图中来创建自定义"进度"视图,并调整容器的宽度以反映进度:

enter image description here

这是一些示例代码.


AnimatedPatternView:所有尺寸、 colored颜色 、速度等属性都位于顶部,以便根据您的喜好轻松调整它们:

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)
    }
}

请注意:这是100它应用作学习工具,不应被视为"生产就绪"."

Ios相关问答推荐

如何调整包含extView的UICollectionViewListCell的大小?

BezierPath 中绘制图像形状轮廓的算法(Canny 边缘检测器)

无法向委托发送数据

mapView swift 导致警告有网格错误

使用异步重载实现Swift协议一致性

如何在Swift/Combine中从timer.publish属性传递两个参数到任意方法

从iOS键盘输入时Flutter TextField输入消失

AppCenter 错误:/usr/bin/xcodebuild 失败,返回码:65

从远程通知启动时,iOS 应用程序加载错误

Swift 共享数据模型在页面之间进行通信.这个怎么运作

setNeedsLayout 和 setNeedsDisplay

如何忽略touch 事件并将它们传递给另一个子视图的 UIControl 对象?

UIScrollview 获取touch 事件

ios 崩溃 EXC_BAD_ACCESS KERN_INVALID_ADDRESS

获取 iOS 上所有联系人的列表

iOS - 确保在主线程上执行

如何在 Objective-C 中组合两个数组?

无效的 iPhone 应用程序二进制文件

IOS - 从 UITextView 中删除所有填充

如果我的分发证书过期会怎样?