[英]Rotate a ImageView around a pivot point in iOS
答案有兩種方法可以使用
PanGesture
並自動使用Timer
。
1.使用UIPanGestureRecognizer:
您可以使用UIPanGestureRecognizer
實現此UIPanGestureRecognizer
。 circleView
是你mainView
和nob
是另一種view
或imageView
將外移到circleView
panGesture = UIPanGestureRecognizer(target: self, action: #selector(panHandler(_:)))
nob.addGestureRecognizer(panGesture)
panHandler(_ :)
定義panHandler(_ :)
@objc func panHandler(_ gesture: UIPanGestureRecognizer) {
let point = gesture.location(in: self)
updateForPoints(point)
}
以下是核心邏輯如何運作。
func updateForPoints(_ point: CGPoint) {
/*
* Parametric equation of circle
* x = a + r cos t
* y = b + r sin t
* a, b are center of circle
* t (theta) is angle
* x and y will be points which are on circumference of circle
*
270
|
_ | _
|
180 -------o------- 360
|
+ | +
|
90
*
*/
let centerOffset = CGPoint(x: point.x - circleView.frame.midX, y: point.y - circleView.frame.midY)
let a: CGFloat = circleView.center.x
let b: CGFloat = circleView.center.y
let r: CGFloat = circleView.layer.cornerRadius - 2.5
let theta: CGFloat = atan2(centerOffset.y, centerOffset.x)
let newPoints = CGPoint(x: a + r * cos(theta), y: b + r * sin(theta))
var rect = nob.frame
rect.origin = newPoints
nob.center = newPoints
}
2.使用Timer自動移動
let totalSeconds: Int = 30 // You can change it whatever you want
var currentSecond: Int = 1
var timer: Timer?
func degreesToRadians(_ degree: CGFloat) -> CGFloat {
/// Will convert the degree (180°) to radians (3.14)
return degree * .pi / 180
}
func angleFromSeconds(_ seconds: Int) -> CGFloat {
/// Will calculate the angle for given seconds
let aSliceAngle = 360.0 / CGFloat(totalSeconds)
let angle = aSliceAngle * CGFloat(seconds) - 90
return angle
}
/// ------------- TIMER METHODS ------------- ///
func startTimer() {
stopTimer()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timeDidUpdate(_ :)), userInfo: nil, repeats: true)
timeDidUpdate(timer!)
}
func stopTimer() {
currentSecond = 1
timer?.invalidate()
timer = nil
}
@objc func timeDidUpdate(_ t: Timer) {
let angle = angleFromSeconds(currentSecond)
let theta = degreesToRadians(angle)
updateAngle(theta: theta, animated: true)
currentSecond += 1
if currentSecond > totalSeconds {
self.stopTimer()
}
}
/// --------------- MAIN METHOD -------------- ///
func updateAngle(theta: CGFloat, animated: Bool) {
let a: CGFloat = circleView.center.x
let b: CGFloat = circleView.center.y
let r: CGFloat = circleView.layer.cornerRadius
let newPoints = CGPoint(x: a + r * cos(theta), y: b + r * sin(theta))
var rect = nob.frame
rect.origin = newPoints
if animated {
UIView.animate(withDuration: 0.1, animations: {
self.nob.center = newPoints
/// Uncomment if your nob is not a circle and you want to maintain the angle too
// self.nob.transform = CGAffineTransform.identity.rotated(by: theta)
})
}
else {
nob.center = newPoints
/// Uncomment if your nob is not a circle and you want to maintain the angle too
//nob.transform = CGAffineTransform.identity.rotated(by: theta)
}
}
如果我理解正確,你想要有一個很好的動畫進度指示器。 當然有很多方法可以實現這一目標。 我將為您提供一些復雜的解決方案,讓您完全掌控動畫 - 動畫期間的變化速度,從不同點開始,隨時暫停,甚至還原動畫。
1)讓我們從完整的工作示例開始。 我們需要很少的屬性:
class ViewController: UIViewController {
var displayLink:CADisplayLink?
var circlePathLayer = CAShapeLayer()
var dottedLine = CAShapeLayer()
var beginTime:TimeInterval?
2)定義顯示鏈接。 它是非常快的觸發器,每個顯示器上的發送事件刷新 - 即每秒60次,可以手動設置,此進度函數將處理進度視圖的正確狀態
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
beginTime = Date().timeIntervalSinceReferenceDate
displayLink = CADisplayLink(target: self, selector: #selector(progress))
displayLink?.add(to: RunLoop.main, forMode: .defaultRunLoopMode)
3)定義你的循環路徑,應該遵循什么
let path = UIBezierPath(arcCenter: view.center, radius: view.center.x - 20, startAngle: -CGFloat.pi / 2, endAngle: CGFloat.pi * 2 - CGFloat.pi / 2, clockwise: true)
4)定義線的虛線和默認動畫。 在CAMediaTimming協議之后有timeOffset
, speed
和beginTime
屬性,我們不想動畫任何東西,並將該層的繪圖設置為零開始狀態
dottedLine.timeOffset = 0
dottedLine.speed = 0
dottedLine.duration = 1
dottedLine.beginTime = dottedLine.convertTime(CACurrentMediaTime(), from: nil)
dottedLine.repeatCount = 1
dottedLine.autoreverses = false
dottedLine.fillColor = nil
dottedLine.fillMode = kCAFillModeBoth
dottedLine.strokeStart = 0.0
dottedLine.strokeColor = UIColor.white.cgColor
dottedLine.lineWidth = 5.0
dottedLine.lineJoin = kCALineJoinMiter
dottedLine.lineDashPattern = [10,10]
dottedLine.lineDashPhase = 3.0
dottedLine.path = path.cgPath
let pathAnimation = CAKeyframeAnimation(keyPath: "strokeEnd")
pathAnimation.duration = 1
pathAnimation.isRemovedOnCompletion = false
pathAnimation.autoreverses = true
pathAnimation.values = [0, 1]
pathAnimation.fillMode = kCAFillModeBoth
dottedLine.add(pathAnimation, forKey: "strokeEnd")
view.layer.addSublayer(dottedLine)
5)小圓相同
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0), radius: 10, startAngle: 0, endAngle:CGFloat.pi * 2, clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = nil
circlePathLayer.addSublayer(shapeLayer)
circlePathLayer.timeOffset = 0
circlePathLayer.speed = 0
circlePathLayer.beginTime = circlePathLayer.convertTime(CACurrentMediaTime(), from: nil)
circlePathLayer.duration = 1
circlePathLayer.repeatCount = 1
circlePathLayer.autoreverses = false
circlePathLayer.fillColor = nil
view.layer.addSublayer(circlePathLayer)
let circleAnimation = CAKeyframeAnimation(keyPath: "position")
circleAnimation.duration = 1
circleAnimation.isRemovedOnCompletion = false
circleAnimation.autoreverses = false
circleAnimation.values = [0, 1]
circleAnimation.fillMode = kCAFillModeBoth
circleAnimation.path = path.cgPath
circlePathLayer.add(circleAnimation, forKey: "position")
}
6)最后進度函數,它經常調用,此時你要設置進度位置 - 在我的例子中設置為30秒,但你可以在這里添加一些條件,不要改變時間偏移 - 暫停,或改變它有不同的處理速度,或放回去。
@objc func progress() {
let time = Date().timeIntervalSinceReferenceDate - (beginTime ?? 0)
circlePathLayer.timeOffset = time / 30
dottedLine.timeOffset = time / 30
}
7)一旦完成動畫,不要忘記釋放資源並使顯示鏈接無效
displayLink?.invalidate()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.