I am trying to automatically scroll slides in my scroll view. I am actually doing this for my onboarding screens.
However I want the timer to pause for a while when user touches the slides and start the same again when he removes his finger.
I am doing something like this but this doesn't work for the case I am asking:
in viewDidLoad -
timer = Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(topImages.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
//offSet = 0 // come back to the first image after the last image
//timer.invalidate()
}
else {
offSet += self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = CGFloat(self.offSet)
}, completion: nil)
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let page = scrollView.contentOffset.x / scrollView.frame.size.width
pageControl.currentPage = Int(page)
self.offSet = page * scrollView.frame.size.width // this updates offset value so that automatic scroll begins from the image you arrived at manually
}
Also I have a second question: How do I start the timer interval again when user manually slides to other other. Right now, when the user slides to another slide before 4 seconds (as 4 seconds is required time to slide to another slide), say 2 seconds, he will slide to next page there after 4-2 = 2 seconds instead of 4 seconds as expected.
I think you should add some flag like
var isSlideTouched = false
In gesture recognizer add
isSlideTouched = true
And some code in autoScroll()
#objc func autoScroll() {
if isSlideTouched {
isSlideTouched = false
return
}
Related
I am trying to animate a simple Loading label text to show 3 dots after it, with each dot having a second of delay.
Here is what i tried:
func animateLoading() {
UIView.animate(withDuration: 1, animations: {self.yukleniyorLabel.text = "Yükleniyor."}, completion: { _ in
UIView.animate(withDuration: 1, animations: {self.yukleniyorLabel.text = "Yükleniyor.."}, completion: { _ in
UIView.animate(withDuration: 1, animations: {self.yukleniyorLabel.text = "Yükleniyor..."})
})
})
}
But what i got is the all 3 dots appear in 1 second alltogether. Not in order. See here: https://streamable.com/yiz6s
What am i doing wrong with the chaining? Thanks in advance.
UIView animate is only for animatable view properties such as frame and background color. self.yukleniyorLabel.text is not an animatable property. So you get no animation.
Just use a Timer or delayed performance to change the text at time intervals.
You can use the scheduled Timer for showing text with three dots on the label with animation: ->
var i = 0
var timer : Timer?
loaderLabel.text = "Loading"
timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector:#selector(ViewController.setText), userInfo: nil, repeats: true)
#objc func setText() {
loaderLabel.text = loaderLabel.text! + "."
i += 1
if i >= 3 {
timer?.invalidate()
}
}
output with animation: -> Loading...
I have implemented an horizontal UICollectionView which scrolls automatically to the next cell after 3 seconds.
Everything works as expected, but I have ran into an issue.
Since the function fires automatically after 3 seconds, with a NSTimer it colides with the user's scroll if both occur at the same time.
Example:
User is scrolling left (backwards) at the same time the 3 second function fires, which will scroll to the right (next cell).
I am wondering if there's a way to let's say, disable the automatic scroll if the user himself is scrolling, and re-enable it after a few seconds, if the user stopped scrolling.
Here's my code:
override func viewDidLoad() {
super.viewDidLoad()
startTimer()
}
fileprivate func startTimer() {
Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(scrollToNextCell), userInfo: nil, repeats: true)
}
#objc fileprivate func scrollToNextCell() {
let cellSize = CGSize(width: width, height: height)
let contentOffset = collectionView.contentOffset
if collectionView.contentSize.width <= collectionView.contentOffset.x + cellSize.width {
let rect = CGRect(x: 0, y: 0, width: cellSize.width, height: cellSize.height)
collectionView.scrollRectToVisible(rect, animated: false)
} else {
let rect = CGRect(x: contentOffset.x + cellSize.width, y: 0, width: cellSize.width, height: cellSize.height)
collectionView.scrollRectToVisible(rect, animated: true)
}
}
What's the best approach here?
Something along:
Collection view detects user scroll it disables the startTimer() function, then if the user stops scrolling, fires another function that waits Xn seconds to fire the startTimer() function again.
Then every time the user scrolls again, it disables again the startTimer() and the new function that enables the startTimer() if it was running.
How can I achieve this?
Make use of the UIScrollViewDelegate
// called on finger up as we are moving
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
Invalidate Timer.
}
// called when scroll view grinds to a halt
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
Restart Timer.
}
Alternative Approach:
Make use if deceleratingproperty. It returns YES if user isn't dragging (touch up) but scroll view is still moving.
#objc fileprivate func scrollToNextCell() {
if (!collectionView.isDecelerating) {
//your code here
}
}
In storyboard I have a ViewController called home.
Home has a red UIView as a subview and is located at the center of Home.
Red View has a LongPressGestureRecognizer that when activated the user can move the red view around the screen.
When the User moves the red View close enough to the left of the screen, a UIView Sidebar pops out.
Within Sidebar are n uniquely colored subviews.
When a user moves red View within the frame of the colored subviews, the colors mix and the color in the sidebar that red view touched changes colors. It changes into the mix.
`
func handleAuditioneeLongPress(sender: UILongPressGestureRecognizer) {
switch sender.state {
case .Began:
hideSideBar()
case .Changed:
let touchPoint = sender.locationInView(self.view)
thumbnailTrackTouchPoint(touchPoint)
case .Ended:
let dropPoint = sender.locationInView(self.view)
thumbnailWasDropped(dropPoint)
default:
break
}
}
func thumbnailTrackTouchPoint(touchPoint: CGPoint){
UIView.setAnimationsEnabled(true)
UIView.animateWithDuration(0.03, delay: 0.0, options: [.BeginFromCurrentState, .CurveEaseOut], animations: {
self.red.center.x = touchPoint.x
self.red.center.y = touchPoint.y - 50
}, completion: nil)
if touchPoint.x < 30{
showSidebar()
isSidebarOut = true
}
if isSidebarOut {
if touchPoint.x > 135 {
hideSideBar()
isSidebarOut = false
}
let location = self.view.convertPoint(touchPoint, toView: sidebar)
let subview = sidebar.hitTest(location, withEvent: nil)
if( subview != nil) {
if let coloredIconController = self.sidebarController.viewToColoredIconController[subview!]
/*
if coloredIconController.view.frame.contains(location) for more than 2 seconds mix the colors
*/
`
While the user maneuvers the sidebar, I don't want accidental mixing.
So I want a check condition. Red should mix if and only if the touchpoint has been contained within a colored view for 2 seconds.
Initiate a scheduled timer when point enters view
myTimer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: "mixing method", userInfo: userInfo, repeats: false)
and when cgpoint get out you just
if myTimer != nil {
myTimer.invalidate()
}
Im trying to make a small game. And there is some problem with the animation. Im new to Swift. So lets take a look. I create a UIImageView picture and want to do animation of this picture appear in a different places on a screen. I believe that the algorithm will look like this:
Infinite loop{
1-GetRandomPlace
2-change opacity from 0 to 1 and back(with smooth transition)
}
Looks simple, but I can't understand how to do it correctly in Xcode.
Here is my test code but it looks useless
Thank you for help and sorry if there was already this question, I can't find it.
class ViewController: UIViewController {
#IBOutlet weak var BackgroundMainMenu:UIImageView!
#IBOutlet weak var AnimationinMenu: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// MoveBackgroundObject(AnimationinMenu)
// AnimationBackgroundDots(AnimationinMenu, delay: 0.0)
// self.AnimationinMenu.alpha = 0
//
// UIImageView.animateWithDuration(3.0,
// delay: 0.0,
// options: UIViewAnimationOptions([.Repeat, .CurveEaseInOut]),
// animations: {
// self.MoveBackgroundObject(self.AnimationinMenu)
// self.AnimationinMenu.alpha = 1
// self.AnimationinMenu.alpha = 0
// },
// completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
AnimationBackgroundDots(AnimationinMenu, delay: 0.0)
}
func ChangeOpacityto1(element: UIImageView){
element.alpha = 0
UIView.animateWithDuration(3.0) {
element.alpha = 1
}
}
func ChangeOpacityto0(element: UIImageView){
element.alpha = 1
UIView.animateWithDuration(3.0){
element.alpha = 0
}
}
func AnimationBackgroundDots(element: UIImageView, delay: Double){
element.alpha = 0
var z = 0
while (z<4){
MoveBackgroundObject(AnimationinMenu)
UIImageView.animateWithDuration(3.0,
animations: {
element.alpha = 0
element.alpha = 1
element.alpha = 0
},
completion: nil)
z++
}
}
func MoveBackgroundObject(element: UIImageView) {
// Find the button's width and height
let elementWidth = element.frame.width
let elementHeight = element.frame.height
// Find the width and height of the enclosing view
let viewWidth = BackgroundMainMenu.superview!.bounds.width
let viewHeight = BackgroundMainMenu.superview!.bounds.height
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - elementWidth
let yheight = viewHeight - elementHeight
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
// Offset the button's center by the random offsets.
element.center.x = xoffset + elementWidth / 2
element.center.y = yoffset + elementHeight / 2
}
}
The problem is in AnimationBackgroundDots.
You are immediately creating 4 animations on the same view but only one can run at a time. What you need to do is wait until one animation is finished (fade in or fade out) before starting a new one.
Also, the animations closure is for setting the state you want your view to animate to. It looks at how your view is at the start, runs animations, then looks at the view again and figures out how to animate between the two. In your case, the alpha of the UIImageView starts at 0, then when animations runs, the alpha ends up being 0 so nothing would animate. You can't create all the steps an animation should take that way.
Want you need to do it move your view and start the fade in animation. The completion closure of fading in should start the fade out animation. The completion closure of the fading out should then start the process all over again. It could look something like this.
func AnimationBackgroundDots(element: UIImageView, times: Int) {
guard times > 0 else {
return
}
MoveBackgroundObject(element)
element.alpha = 1
// Fade in
UIView.animateWithDuration(3.0, animations: {
element.alpha = 1
}, completion: { finished in
// Fade Out
UIView.animateWithDuration(3.0, animations: {
element.alpha = 0
}, completion: { finished in
// Start over again
self.AnimationBackgroundDots(element, times: times-1)
})
})
}
You called also look at using keyframe animations but this case is simple enough that theres no benefit using them.
Also as a side note. The function naming convention in swift is to start with a lowercase letter, so AnimationBackgroundDots should be animationBackgroundDots.
I am making a progress bar by increasing the with of a simple image:
let progressBar = createProgressBar(width: self.view.frame.width, height: 60.0)
let progressBarView = UIImageView(image: progressBar)
progressBarView.frame = CGRect(x: 0, y: 140, width: 0, height: 60)
UIView.animateWithDuration(60.0, delay: 0.0, options: [], animations: {
progressBarView.frame.size.width = self.backgroundView.frame.size.width
}, completion: {_ in
print("progress completed")
}
)
This works as expected, but I am having problems when changing views using a TabBarController. When I change view, I would like the progress bar to continue animating in the background, such that I can go back to this view to check on progress, but instead it does end immediately when I change views, and the completion block is called.
Why does this happen, and how to fix it?
When you tap another tabBar item, the viewController that is performing animation will be in a state of viewDidDisappear and the animation will be removed.
Actually, it is not recommended to perform any animation when the viewController is not presented in the front of the screen.
To continue the interrupted animation progress, you have to maintain the current states of the animation and restore them when the tabBar item switched back.
For example, you may hold some instance variables to keep the duration, progress, beginWidth and endWidth of the animation. And you can restore the animation in viewDidAppear:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// `self.duration` is the total duration of the animation. In your case, it's 60s.
// `self.progress` is the time that had been consumed. Its initial value is 0s.
// `self.beginWidth` is the inital width of self.progressBarView.
// `self.endWidth`, in your case, is `self.backgroundView.frame.size.width`.
if self.progress < self.duration {
UIView.animateWithDuration(self.duration - self.progress, delay: 0, options: [.CurveLinear], animations: {
self.progressBarView.frame.size.width = CGFloat(self.endWidth)
self.beginTime = NSDate() // `self.beginTime` is used to track the actual animation duration before it interrupted.
}, completion: { finished in
if (!finished) {
let now = NSDate()
self.progress += now.timeIntervalSinceDate(self.beginTime!)
self.progressBarView.frame.size.width = CGFloat(self.beginWidth + self.progress/self.duration * (self.endWidth - self.beginWidth))
} else {
print("progress completed")
}
}
)
}
}