在 Swift 动画中实现 CSS
Posted
技术标签:
【中文标题】在 Swift 动画中实现 CSS【英文标题】:Implement CSS into Swift Animation 【发布时间】:2020-06-28 13:59:08 【问题描述】:我有一个在 html 中用 CSS 编写的加载器。 我想在我的 ios 应用中实现完全相同的目标。
这里是加载器的 CSS。
<!DOCTYPE html>
<html>
<head>
<style>
@-webkit-keyframes spin
0%
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
50%
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
100%
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
@keyframes spin
0%
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
100%
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
.loader
display: block;
width: 75px;
height: 75px;
margin: 0 auto;
border-radius: 50%;
border: 1px solid transparent;
border-top-color: #ff9000;
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
position: fixed;
z-index: 9999;
left: 0;
right: 0;
top: 50%;
transform: translateY(-50%)
.loader:before
content: "";
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
border-radius: 50%;
border: 1px solid transparent;
border-top-color: #f00;
-webkit-animation: spin 3s linear infinite;
animation: spin 3s linear infinite
.loader:after
content: "";
position: absolute;
top: 11px;
left: 11px;
right: 11px;
bottom: 11px;
border-radius: 50%;
border: 1px solid transparent;
border-top-color: #0a00b2;
-webkit-animation: spin 1.5s linear infinite;
animation: spin 1.5s linear infinite
</style>
</head>
<body>
<div class="loader"></div>
</body>
</html>
如果有任何库、工具或任何东西可以帮助我在我的 iOS 应用程序中获得完全相同的工具,那将非常有帮助。
我尝试修改相同的外观,但即使调整了这么多,我也无法获得完全相同的结果。
提前致谢。
【问题讨论】:
你可以先分解发生了什么,然后翻译成 SwiftUI。从我所见(尽管对 css 的经验为零),您有 3 个形状(弧线),它们以 1.5、2 和 3 秒的持续时间无限期地围绕同一中心旋转(具有线性时序曲线)。所以问题可能变成“我如何在 SwiftUI 中制作形状并为其设置动画?” @Alladinian我试过了,但我无法获得此处发布的相同体验 如果您编辑问题以包含所需效果的动画 GIF,您将获得更多帮助。您还需要包含您编写的 Swift 代码。 @robmayoff 只需点击上面代码下方的“运行代码 sn-p”按钮另外,我已经添加了我的代码,您可以查看它,如果您有任何改进建议,那么您最受欢迎。 重温这一点,我无法理解为什么我发布了一个SwiftUI
实现(每个人都非常好,不让我在这件事上喊出来????)。无论如何,很高兴您找到了解决方案。如果有人对 SUI 方法感兴趣,我会保留答案。
【参考方案1】:
似乎是一个有趣的练习,所以这里有一个可能的解决方案:
首先,我们需要一种制作弧线的方法。我们可以在 SwiftUI 中使用 Shape
来做到这一点:
struct Arc: Shape
let radius: CGFloat
let angle: Double
func path(in rect: CGRect) -> Path
Path path in
path.addArc(center: rect.center,
radius: radius,
startAngle: Angle(degrees: 0),
endAngle: Angle(degrees: angle),
clockwise: false)
.strokedPath(StrokeStyle(lineWidth: 1))
extension CGRect
var center: CGPoint
.init(x: midX, y: midY)
请注意,这里有两个输入,radius
和 angle
(以度为单位)。我们还用 1px 的线描边路径,并定义了一个方便的扩展来获取矩形的中心。
然后我们需要一些东西,可以让任何视图在一段时间内无限旋转:
private extension View
func rotationAnimation(duration: Double) -> Animation
Animation.linear(duration: duration / 2)
.repeatForever(autoreverses: false)
func rotateFor(_ duration: Double, when isAnimating: Bool) -> some View
self.rotationEffect(Angle(degrees: isAnimating ? 360 : 0.0))
.animation(isAnimating ? rotationAnimation(duration: duration) : .default)
请注意,在上面的 sn-p 中,我将持续时间减半以匹配 css(我注意到在 50% 处有一个完整旋转的关键帧)
最后,让我们把一些形状放在一个ZStack
:
struct LoadingView: View
@State var isAnimating: Bool = false
var body: some View
ZStack
Arc(radius: 20, angle: 90)
.rotateFor(1.5, when: isAnimating)
.foregroundColor(.blue)
Arc(radius: 25, angle: 90)
.rotateFor(3.0, when: isAnimating)
.foregroundColor(.red)
Arc(radius: 30, angle: 90)
.rotateFor(2.0, when: isAnimating)
.foregroundColor(.orange)
.frame(width: 100, height: 100)
.onAppear
self.isAnimating = true
预览结果如下:
struct LoadingView_Previews: PreviewProvider
static var previews: some View
LoadingView()
我猜你可以使用参数来让它恰到好处
【讨论】:
【参考方案2】:我想我已经实现了我最初想要的。
import UIKit
class CustomLoader: UIViewController
static let shared = CustomLoader()
var arcsNotAdded: Bool = true
override func viewDidLoad()
super.viewDidLoad()
self.view.backgroundColor = .clear
func showLoader(_ interactionEnabled: Bool = true)
if let appDelegate = UIApplication.shared.delegate, let window = appDelegate.window, let rootViewController = window?.rootViewController
var topViewController = rootViewController
let topVCArray = topViewController.children
for obj in topVCArray where obj is CustomLoader return
while topViewController.presentedViewController != nil
topViewController = topViewController.presentedViewController!
if topVCArray.count == 1 && topVCArray.first is InitialLandingController //HOTFix for initial landing controller
topViewController = topVCArray.first!
topViewController.addChild(self)
topViewController.view.addSubview(view)
didMove(toParent: topViewController)
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.frame = CGRect(topViewController.view.center.x, topViewController.view.center.y, 20, 20)
view.center = topViewController.view.center
kAppDelegate.window?.isUserInteractionEnabled = interactionEnabled
arcsNotAdded ? configureArcs() : debugPrint("Arcs are already added")
func hideLoader()
self.willMove(toParent: nil)
self.view.removeFromSuperview()
self.removeFromParent()
kAppDelegate.window?.isUserInteractionEnabled = true
private func configureArcs()
arcsNotAdded = false
let arcStartAngle: CGFloat = -CGFloat.pi / 2
let arcEndAngle: CGFloat = 0
let centrePoint = CGPoint(x: view.bounds.midX, y: view.bounds.midY)
let outerCircle = CAShapeLayer()
let middleCircle = CAShapeLayer()
let innerCircle = CAShapeLayer()
let circleColorOuter: CGColor = UIColor(red: 255/255, green: 144/255, blue: 0/255, alpha: 1.0).cgColor
let circleColorMiddle: CGColor = UIColor(red: 255/255, green: 0/255, blue: 0/255, alpha: 1.0).cgColor
let circleColorInner: CGColor = UIColor(red: 10/255, green: 0/255, blue: 178/255, alpha: 1.0).cgColor
let outerPath = UIBezierPath(arcCenter: centrePoint, radius: CGFloat(77/2), startAngle: arcStartAngle, endAngle: arcEndAngle, clockwise: true)
let midPath = UIBezierPath(arcCenter: centrePoint, radius: CGFloat(65/2), startAngle: arcStartAngle, endAngle: arcEndAngle, clockwise: true)
let innerPath = UIBezierPath(arcCenter: centrePoint, radius: CGFloat(53/2), startAngle: arcStartAngle, endAngle: arcEndAngle, clockwise: true)
configureArcLayer(outerCircle, forView: view, withPath: outerPath.cgPath, withBounds: view.bounds, withColor: circleColorOuter)
configureArcLayer(middleCircle, forView: view, withPath: midPath.cgPath, withBounds: view.bounds, withColor: circleColorMiddle)
configureArcLayer(innerCircle, forView: view, withPath: innerPath.cgPath, withBounds: view.bounds, withColor: circleColorInner)
animateArcs(outerCircle, middleCircle, innerCircle)
private func configureArcLayer(_ layer: CAShapeLayer, forView view: UIView, withPath path: CGPath, withBounds bounds: CGRect, withColor color: CGColor)
layer.path = path
layer.frame = bounds
layer.lineWidth = 1.5
layer.strokeColor = color
layer.fillColor = UIColor.clear.cgColor
layer.isOpaque = true
view.layer.addSublayer(layer)
private func animateArcs(_ outerCircle: CAShapeLayer, _ middleCircle: CAShapeLayer, _ innerCircle: CAShapeLayer)
DispatchQueue.main.async
let outerAnimation = CABasicAnimation(keyPath: "transform.rotation")
outerAnimation.toValue = 2 * CGFloat.pi
outerAnimation.duration = 1.5
outerAnimation.repeatCount = Float(UINT64_MAX)
outerAnimation.isRemovedOnCompletion = false
outerCircle.add(outerAnimation, forKey: "outerCircleRotation")
let middleAnimation = outerAnimation.copy() as! CABasicAnimation
middleAnimation.duration = 1
middleCircle.add(middleAnimation, forKey: "middleCircleRotation")
let innerAnimation = middleAnimation.copy() as! CABasicAnimation
innerAnimation.duration = 0.75
innerCircle.add(innerAnimation, forKey: "middleCircleRotation")
【讨论】:
以上是关于在 Swift 动画中实现 CSS的主要内容,如果未能解决你的问题,请参考以下文章