Good day,
I'm facing a wired behavior in a UIScrollView here. I'm implementing a custom circular UIScrollView and in a point I've to set the origin of the scroll view to a point
scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
After that I change the content offset using animation to a specific point
scrollView.setContentOffset(CGPoint(x: point, y: 0), animated: true)
The problem is that although point is correct (ex. 400) scrollview sometimes jumps to another point so code like the following should print 10 if point = 10 but sometimes it prints other values.
scrollView.setContentOffset(CGPoint(x: point, y: 0), animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
print("Current x: \(self.tabsScrollView.contentOffset.x)")})
Although I made sure that no other animation was in progress.
I found that someone was suggesting something like that
UIView.animate(withDuration: 0.5, animations: {
self.scrollView.contentOffset.x = point
})
And surprisingly it did work, but not animated like default.
Any ideas?
Related
I am using a UICollectionView to display a grid of cells with 3 columns. When a cell is selected, I'd like to zoom into the entire collection with the selected cell in the centre. I'd also like to fade out the surrounding cells and keep the selected on at full opacity.
The following code scales the grid but doesn't scroll to the selected cell:
UIView.animate(withDuration: 0.35, animations: {
self.collectionView.transform = CGAffineTransform(scaleX: 3, y: 3)
})
I was hoping I could do it by changing the UICollectionViewFlowLayout and the UICollectionView frame but I'm not sure whether I'm going down the wrong path with this (the following code just moves all the cells over to the left):
UIView.animate(withDuration: 0.35, animations: {() -> Void in
self.collectionView.setCollectionViewLayout(self.zoomedGridLayout, animated: true)
let zoomedRect = CGRect(x: -rect.width, y: 0, width: rect.width*3, height: rect.height)
self.collectionView.frame = zoomedRect
self.collectionView.collectionViewLayout.invalidateLayout()
})
The collection view doesn't need to be scrollable after the animation (I intend to use it only as a transition to another view).
I try to rotate a uiview (height & width of 100) with an angle of 90 degrees with an own anchor point. After changing the anchor point I translate my view so that both changes cancel each other out.
When I rotate the view with (Double.pi) it animates as expected but when I change to (Double.pi/2) the view is jumping up.
testView.layer.anchorPoint = CGPoint(x: 0.5, y: 1)
testView.layer.transform = CATransform3DMakeTranslation(0, -50, 0)
UIView.animate(withDuration: 2.3, delay: 0, options: .curveLinear, animations: { () -> Void in
var allTransofrom = self.testView.layer.transform
var c3d = CATransform3DIdentity
c3d.m34 = 2.5 / (2000)
let rot = CATransform3DRotate(c3d, CGFloat(Double.pi/2), 1, 0, 0)
allTransofrom = CATransform3DConcat(allTransofrom, rot)
self.testView.layer.transform = allTransofrom
}, completion: {(finished) in
} )
full code here
I was able to found a solution by myself but
I still don't know where the jump is coming from.
Instead of doing a translation via CGAffineTransform.translatedBy or CATransform3DMakeTranslation I simply adapted my NSLayoutConstraint.
testViewAnchors[0].constant = testViewAnchors[0].constant + CGFloat(50)
In my case an animation for the "translation" wasn't necessary but as it is possible to animate NSLayoutConstraints it shouldn't be a problem.
I am implementing a rotating digit view like this:
https://github.com/jonathantribouharet/JTNumberScrollAnimatedView
But, instead of scrolling from say 1 to 9, I want the animation to continue scrolling (1 to 9 then back to 1 to 9), until I manually stop it.
I implement this by adding UILabel subviews 1-9, vertically stacked, and add an extra label for number 1 at the bottom. When rotating to the bottom (showing the bottom label 1), in the animation completion closure, I set the transform back to origin, showing top label 1, and restart rotating animation.
This works fine, but since each rotation (1-9-1) is separate, the animation from 1-9 accelerates and then stop at 1, and pick up speed at the next iteration. This is not what I want, I want the rotation to maintain a constant speed between the iterations.
How can I do this with UIView animation?
Here is the code:
public func startAnimation(){
UIView.setAnimationCurve(UIViewAnimationCurve.easeIn)
UIView.animate(withDuration: TimeInterval(self.loopDuration), animations: { [unowned self] in
self.container!.transform = CGAffineTransform(translationX: 0, y: CGFloat(-(self.endNumber-self.startNumber+1)*(self.size)))
}) { [unowned self] (completed) in
if self.isAnimationStopped {
self.container!.transform = CGAffineTransform(translationX: 0, y: 0)
UIView.setAnimationCurve(UIViewAnimationCurve.easeOut)
UIView.animate(withDuration: TimeInterval(self.loopDuration), animations: {
self.container!.transform = CGAffineTransform(translationX: 0, y: CGFloat(-self.targetY))
})
} else {
self.container!.transform = CGAffineTransform(translationX: 0, y: 0)
self.startAnimation()
}
}
}
By default UIView animation uses ease-in, ease-out animation timing, where the animation accelerates smoothly, runs, then decelerates to a stop.
You need to use a longer form of UIView.animate, animate(withDuration:delay:options:animations:completion:) that takes an options parameter, and specify a timing curve of .curveLinear.
Just specify 0.0 for the delay and options: .curveLinear
Edit:
Note that if you need to specify multiple options, like .curveLinear and .allowUserInteraction you would use OptionSet syntax, like options: [.curveLinear, .allowUserInteraction]
I'm making squares appear at the top of the screen, run till they reach the bottom and then disappear. This is done adding a new UIView to the ViewController's view and then, after the animation is completed, removing it from the subviews array. To add some beauty to all of this, I apply a random rotation using a CGAffineTransform at the moment of their creation.
The curious thing here is that when they are moving, they are being scaled randomly! I know this must be related to the rotation, because if I don't apply it, all the squares move perfectly without scaling, but when I apply it, it happens, so...
I hope someone could test it and tell me what's happening... Why are squares scaling???
This is the code:
import UIKit
class ViewController: UIViewController {
var timer: Timer!
override func viewDidLoad() {
timer = Timer.scheduledTimer(
timeInterval: 1,
target: self,
selector: #selector(createView),
userInfo: nil,
repeats: true
)
}
#objc private func createView () {
let v = UIView(frame: CGRect(
x: self.view.bounds.width / 2 - 50,
y: -100,
width: 100,
height: 100
))
v.backgroundColor = UIColor.red
v.transform = CGAffineTransform(
rotationAngle: CGFloat(Double(arc4random_uniform(360)) * M_PI / 180)
)
self.view.addSubview(v)
UIView.animate(withDuration: 5, delay: 0, options: [.curveLinear], animations: {
v.frame.origin.y = self.view.bounds.height
}, completion: {
_ in
v.removeFromSuperview()
})
print(self.view.subviews.count)
}
}
The first rule of animation club is don't manipulate a view's frame when you've changed it's transform.
If a view's transform is not the identity transform then the frame is undefined, and trying to change the frame will result in undefined behavior. It sounds like you're experiencing some of that undefined behavior.
You should refactor your code to manipulate the view's center property rather than the frame, or apply your offset using the transform, but that is tricky since rotation changes the frame of reference for the offset.
I recommend adjusting the view's center property.
When the user lets go of scrolling in a UICollectionView, I want it to scroll to a relevant point. In this example, I'll use CGPoint(x: 0, y: 100).
I've previously tried doing this:
func scrollViewWillBeginDecelerating(scrollView: UIScrollView) {
self.collectionView.setContentOffset(CGPoint(x: 0, y: 100), animated: true)
}
But that gives an animation that starts with a CurveEaseIn. If the user were to let go as they were dragging, this would give an odd appearance as the dragging suddenly halts as they lift their finger, then starts moving again.
So I tried doing this with my own animation instead:
UIView.animateWithDuration(
0.3,
delay: 0,
options: UIViewAnimationOptions.CurveEaseOut,
animations: {
() -> Void in
self.collectionView.setContentOffset(CGPoint(x: 0, y: 100), animated: false)
}, completion: nil)
The animation take place just like I'd want, but the cells which would be off screen at the end of the animation disappear immediately. The same thing happens if I replace setContentOffset() with contentOffset =.
So then somebody suggest that instead, I use the scrollViewWillEndDragging:withVelocity:targetContentOffset: method. So I did:
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
targetContentOffset.memory = CGPoint(x: 0, y: 100)
}
What happens now is the animation occurs with the given velocity, so if the user lifts their finger, then it slowly makes its way to the desired location, with 0 velocity.