Stop the autoscroll in scrollview on user interaction swift - ios

I using the pagecontroller in my application.
it is working fine every thing it will scroll automatically for every 3 sec time and current position also changed based on user custom swipe.
But I having one issue here when user manually swipe the scrollview on that time I want to stop the auto scroll.
Please find with the my following code.
var slides:[Slide] = [];
var offSet: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
slides = createSlides()
setupSlideScrollView(slides: slides)
pageControl.numberOfPages = slides.count
pageControl.currentPage = 0
view.bringSubview(toFront: pageControl)
let timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)
}
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(slides.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
offSet = 0 // come back to the first image after the last image
}
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)
}
}

Add scroll view delegate and use scrollViewDidEndDragging methods when it calls just reset your timer so after 3 sec start again it working as it is.

The code is untested, You should check with current index with your total number slides count
Create one global index like below and change your conditions. And you need to manage this index while swipe function as well and Invalidate your timer while swipe. Your timer object should be in global
timer.invalidate()
var currentPage = 0
#objc func autoScroll() {
let totalPossibleOffset = 0
if currentPage == slides.count {
currentPage = 0 // come back to the first image after the last image
totalPossibleOffset = 0
}
else {
currentPage = currentPage + 1
totalPossibleOffset = currentPage * self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = CGFloat(totalPossibleOffset)
}, completion: nil)
}
}

Related

iOS Spin UIImageView with deceleration motion at random position

I trying to create a spin wheel which rotate on tap and it rotates for certain period of time and stops at some random circular angle.
import UIKit
class MasterViewController: UIViewController {
lazy var imageView: UIImageView = {
let bounds = self.view.bounds
let v = UIImageView()
v.backgroundColor = .red
v.frame = CGRect(x: 0, y: 0,
width: bounds.width - 100,
height: bounds.width - 100)
v.center = self.view.center
return v
}()
lazy var subView: UIView = {
let v = UIView()
v.backgroundColor = .black
v.frame = CGRect(x: 0, y: 0,
width: 30,
height: 30)
return v
}()
var dateTouchesEnded: Date?
var dateTouchesStarted: Date?
var deltaAngle = CGFloat(0)
var startTransform: CGAffineTransform?
var touchPointStart: CGPoint?
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.addSubview(self.subView)
self.view.addSubview(self.imageView)
self.imageView.isUserInteractionEnabled = true
self.setupGesture()
}
private func setupGesture() {
let gesture = UITapGestureRecognizer(target: self,
action: #selector(handleGesture(_:)))
self.view.addGestureRecognizer(gesture)
}
#objc
func handleGesture(_ sender: UITapGestureRecognizer) {
var timeDelta = 1.0
let _ = Timer.scheduledTimer(withTimeInterval: 0.2,
repeats: true) { (timer) in
if timeDelta < 0 {
timer.invalidate()
} else {
timeDelta -= 0.03
self.spinImage(timeDelta: timeDelta)
}
}
}
func spinImage(timeDelta: Double) {
print("TIME DELTA:", timeDelta)
let direction: Double = 1
let rotation: Double = 1
UIView.animate(withDuration: 5,
delay: 0,
options: .curveEaseOut,
animations: {
let transform = self.imageView.transform.rotated(
by: CGFloat(direction) * CGFloat(rotation) * CGFloat(Double.pi)
)
self.imageView.transform = transform
}, completion: nil)
}
}
I tried via above code, but always stops on initial position
i.e. the initial and final transform is same.
I want it to be random at every time.
One thing you can do is make a counter with a random number within a specified range. When the time fires, call spinImage then decrement the counter. Keep doing that until the counter reaches zero. This random number will give you some variability so that you don't wind up with the same result every time.
#objc func handleGesture(_ sender: UITapGestureRecognizer) {
var counter = Int.random(in: 30...33)
let _ = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { (timer) in
if counter < 0 {
timer.invalidate()
} else {
counter -= 1
self.spinImage()
}
}
}
In spinImage, instead of rotating by CGFloat.pi, rotate by by CGFloat.pi / 2 so that you have four possible outcomes instead of two.
func spinImage() {
UIView.animate(withDuration: 2.5,
delay: 0,
options: .curveEaseOut,
animations: {
let transform = self.imageView.transform.rotated(
by: CGFloat.pi / 2
)
self.imageView.transform = transform
}, completion: nil)
}
You may want to mess around with the counter values, the timer interval, and the animation duration to get the effect that you want. The values I chose here are somewhat arbitrary.

UIView.animate seems to freeze other view updates

I have a UIView that is a countdown clock, which I animate with UIView.animate. There is also a running timer that shows the time on a UILabel, when the timer is fired. When the game is in transition (during the count down), the UILabel stops changing. I've put a "print" statement into the timer function and it shows up as expected.
I've been searching for hints about maybe the UIVIew.animate freezes other views, but I see multiple apps moving views at the same time. Maybe I'm supposed to put things on different thread?
#objc func timerUpdate () {
if gameIsRunning {
let endTime = Date.init()
let difference = endTime.timeIntervalSince(gameClock)
let formattedString = String(format: "%3.1f",kDefaultGameClock - difference)
timerLabel.text = "\(formattedString)"
}
}
private func countDownFrom (x: Int) {
if x != 0 && x > 0 {
UIView.animate(withDuration: 1.0,
delay: 0.0,
usingSpringWithDamping: 0.5,
initialSpringVelocity:1.0,
options: .curveEaseOut,
animations: {
self.successLabel.text = "\(x)"
self.successLabel.transform = self.successLabel.transform.scaledBy(x: 4.0, y: 4.0)
}, completion: {_ in
self.successLabel.transform = self.successLabel.transform.scaledBy(x: 0.250, y: 0.250)
if (x - 1 > 0) {
self.countDownFrom(x: x - 1)
} else {
self.successLabel.text = kDefaultGoodGame
self.successLabel.isHidden = true
self.startTheGame()
self.updateScreen()
}
})
}
}
When I tried your code in an empty project, I was able to update both labels.
If timerLabel isn't being updated, it could be a thread issue. UI updates should be on the main thread.
DispatchQueue.main.async {
self.timerLabel.text = "\(formattedString)"
}
For what it's worth, here is what I have working in an empty project.
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var successLabel: UILabel!
let kDefaultGameClock = 30.0
var gameIsRunning = false
var gameClock = Date()
override func viewDidLoad() {
super.viewDidLoad()
start()
}
func start() {
gameIsRunning = true
countDownFrom(x: 30)
Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(timerUpdate), userInfo: nil, repeats: true)
}
#objc func timerUpdate() {
if gameIsRunning {
let endTime = Date.init()
let difference = endTime.timeIntervalSince(gameClock)
let formattedString = String(format: "%3.1f", kDefaultGameClock - difference)
timerLabel.text = "\(formattedString)" // Could be a main thread issue here?
}
}
private func countDownFrom(x: Int) {
if x != 0 && x > 0 {
UIView.animate(withDuration: 1.0,
delay: 0.0,
usingSpringWithDamping: 0.5,
initialSpringVelocity:1.0,
options: .curveEaseOut,
animations: {
self.successLabel.text = "\(x)"
self.successLabel.transform = self.successLabel.transform.scaledBy(x: 4.0, y: 4.0)
}, completion: {_ in
self.successLabel.transform = self.successLabel.transform.scaledBy(x: 0.250, y: 0.250)
if (x - 1 > 0) {
self.countDownFrom(x: x - 1)
} else {
self.successLabel.text = ""
self.successLabel.isHidden = true
//self.startTheGame()
//self.updateScreen()
}
})
}
}
A little off topic, but you might want to declare your Timer as a variable to invalidate it once you're done updating the timerLabel.

Move scrollview automatically with page controller

Hi I am using the PageController in my application.
I have inserted all the data on scrollView.
What I want do is, I want to move the scrollview automatically like page control with specific time.
When the scrollView reaches to last page it again recall all pages speedy and start from first onwards.
I want to load/get first page very smoothly after last page completion.
Find my code with following.
var slides:[Slide] = [];
var offSet: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
slides = createSlides()
setupSlideScrollView(slides: slides)
pageControl.numberOfPages = slides.count
pageControl.currentPage = 0
view.bringSubview(toFront: pageControl)
let timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)
}
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(slides.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
offSet = 0 // come back to the first image after the last image
}
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)
}
}
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(slides.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
offSet = 0 // come back to the first image after the last image
}
else {
offSet += self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.1, delay: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.scrollView.scroll(topOffset:offSet, animated: true)
}, completion: nil)
}
}
extension UIScrollView {
// Bonus: Scroll
func scroll(topOffset:Float, animated: Bool) {
let ofSetValue = CGPoint(x: topOffset, y: 0)
setContentOffset(ofSetValue, animated: animated)
}
}
Use the following code working fine.
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(slides.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
offSet = 0 // come back to the first image after the last image
}
else {
offSet += self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.0, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = CGFloat(self.offSet)
}, completion: nil)
}
}

How to Animate a UIView n number of times within a loop and track each animation event

UIView.animate(withDuration: 3.0, animations: {
// UIView.setAnimationRepeatCount(3.0)
self.leadng.constant = self.view.frame.size.width + self.vwObj.frame.size.width
self.view.layoutIfNeeded()
}, completion: { finished in
UIView.animate(withDuration: 3.0, animations: {
self.leadng.constant = -(self.vwObj.frame.size.width)
})
})
This is the code i am using for animating a UIView. What i am trying to do is to animate the UIView n number of times. That can be done using UIView.setAnimationRepeatCount(3.0), but the problem is i am having an array of different images which i want to show within the UiView for each animation. Anyone having any idea about how to do that
Use the animatedImage property of UIImage to animate multiple images inside a imageview.
ImageView.image = UIImage.animatedImage(with: myImages, duration: 1.0)
where myImages is array of images you want to animate.
Follow this link for details.
Edit: Definitely a very crude way, but it works.
Declared this at top of class.
let arrayColors = [UIColor.red , UIColor.green, UIColor.blue]
var animationCount = 0
I called animateView() in viewdidload
func animateView() {
self.vwObj.backgroundColor = arrayColors[animationCount]
UIView.animate(withDuration: 3.0, animations: {
self.leadng.constant = self.view.frame.size.width + self.vwObj.frame.size.width
self.view.layoutIfNeeded()
}, completion: { finished in
UIView.animate(withDuration: 3.0, animations: {
self.leadng.constant = 0
self.view.layoutIfNeeded()
}, completion: { finished in
self.animationCount = self.animationCount + 1
if self.animationCount < 3 {
self.animateView()
}
})
})
}
Insted of array of colors, you can use array of images to set imageview.image = arrayImage[animationCount]
Seems solved by the below code:
UIView.animate(withDuration: 3.0, animations: {
print ("Animation Started")
self.leadng.constant = self.view.frame.size.width + self.vwObj.frame.size.width
self.view.layoutIfNeeded()
print ("BeginCounter --- (self.counter)")
}, completion: { finished in
UIView.animate(withDuration: 3.5, animations: {
self.leadng.constant = -(self.vwObj.frame.size.width)
print ("AnimationCompleted")
self.counter = self.counter + 1
print ("CompleteCounter --- \(self.counter)")
//self.displayAnimation()
if (self.counter == 3) {
self.timer?.invalidate()
self.counter = 0
}
})
})
& calling---- timer = Timer.scheduledTimer(timeInterval: 4.0, target: self, selector: #selector(ViewController.displayAnimation), userInfo: nil, repeats: true)

How do I hide/show tabBar when tapped using Swift in iOS8

I am trying to mimic the UINavigationController's new hidesBarsOnTap with a tab bar. I have seen many answers to this that either point to setting the hidesBottomBarWhenPushed on a viewController which only hides it entirely and not when tapped.
#IBAction func tapped(sender: AnyObject) {
// what goes here to show/hide the tabBar ???
}
thanks in advance
EDIT: as per the suggestion below I tried
self.tabBarController?.tabBar.hidden = true
which does indeed hide the tabBar (toggles true/false on tap), but without animation. I will ask that as a separate question though.
After much hunting and trying out various methods to gracefully hide/show the UITabBar using Swift I was able to take this great solution by danh and convert it to Swift:
func setTabBarVisible(visible: Bool, animated: Bool) {
//* This cannot be called before viewDidLayoutSubviews(), because the frame is not set before this time
// bail if the current state matches the desired state
if (tabBarIsVisible() == visible) { return }
// get a frame calculation ready
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
let offsetY = (visible ? -height! : height)
// zero duration means no animation
let duration: TimeInterval = (animated ? 0.3 : 0.0)
// animate the tabBar
if frame != nil {
UIView.animate(withDuration: duration) {
self.tabBarController?.tabBar.frame = frame!.offsetBy(dx: 0, dy: offsetY!)
return
}
}
}
func tabBarIsVisible() -> Bool {
return (self.tabBarController?.tabBar.frame.origin.y)! < self.view.frame.maxY
}
// Call the function from tap gesture recognizer added to your view (or button)
#IBAction func tapped(_ sender: Any?) {
setTabBarVisible(visible: !tabBarIsVisible(), animated: true)
}
Loved Michael Campsall's answer. Here's the same code as extension, if somebody is interested:
Swift 2.3
extension UITabBarController {
func setTabBarVisible(visible:Bool, animated:Bool) {
// bail if the current state matches the desired state
if (tabBarIsVisible() == visible) { return }
// get a frame calculation ready
let frame = self.tabBar.frame
let height = frame.size.height
let offsetY = (visible ? -height : height)
// animate the tabBar
UIView.animateWithDuration(animated ? 0.3 : 0.0) {
self.tabBar.frame = CGRectOffset(frame, 0, offsetY)
self.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height + offsetY)
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
}
}
func tabBarIsVisible() ->Bool {
return self.tabBar.frame.origin.y < CGRectGetMaxY(self.view.frame)
}
}
Swift 3
extension UIViewController {
func setTabBarVisible(visible: Bool, animated: Bool) {
//* This cannot be called before viewDidLayoutSubviews(), because the frame is not set before this time
// bail if the current state matches the desired state
if (isTabBarVisible == visible) { return }
// get a frame calculation ready
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
let offsetY = (visible ? -height! : height)
// zero duration means no animation
let duration: TimeInterval = (animated ? 0.3 : 0.0)
// animate the tabBar
if frame != nil {
UIView.animate(withDuration: duration) {
self.tabBarController?.tabBar.frame = frame!.offsetBy(dx: 0, dy: offsetY!)
return
}
}
}
var isTabBarVisible: Bool {
return (self.tabBarController?.tabBar.frame.origin.y ?? 0) < self.view.frame.maxY
}
}
I had to adapt the accepted answer to this question a bit. It was hiding the bar but my view wasn't sizing itself appropriately so I was left with a space at the bottom.
The following code successfully animates the hiding of the tab bar while resizing the view to avoid that issue.
Updated for Swift 3 (now with less ugly code)
func setTabBarVisible(visible: Bool, animated: Bool) {
guard let frame = self.tabBarController?.tabBar.frame else { return }
let height = frame.size.height
let offsetY = (visible ? -height : height)
let duration: TimeInterval = (animated ? 0.3 : 0.0)
UIView.animate(withDuration: duration,
delay: 0.0,
options: UIViewAnimationOptions.curveEaseIn,
animations: { [weak self] () -> Void in
guard let weakSelf = self else { return }
weakSelf.tabBarController?.tabBar.frame = frame.offsetBy(dx: 0, dy: offsetY)
weakSelf.view.frame = CGRect(x: 0, y: 0, width: weakSelf.view.frame.width, height: weakSelf.view.frame.height + offsetY)
weakSelf.view.setNeedsDisplay()
weakSelf.view.layoutIfNeeded()
})
}
func handleTap(recognizer: UITapGestureRecognizer) {
setTabBarVisible(visible: !tabBarIsVisible(), animated: true)
}
func tabBarIsVisible() -> Bool {
guard let tabBar = tabBarController?.tabBar else { return false }
return tabBar.frame.origin.y < UIScreen.main.bounds.height
}
Older Swift 2 Version
func setTabBarVisible(visible: Bool, animated: Bool) {
// hide tab bar
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
var offsetY = (visible ? -height! : height)
println ("offsetY = \(offsetY)")
// zero duration means no animation
let duration:NSTimeInterval = (animated ? 0.3 : 0.0)
// animate tabBar
if frame != nil {
UIView.animateWithDuration(duration) {
self.tabBarController?.tabBar.frame = CGRectOffset(frame!, 0, offsetY!)
self.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height + offsetY!)
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
return
}
}
}
#IBAction func handleTap(recognizer: UITapGestureRecognizer) {
setTabBarVisible(!tabBarIsVisible(), animated: true)
}
func tabBarIsVisible() -> Bool {
return self.tabBarController?.tabBar.frame.origin.y < UIScreen.mainScreen().bounds.height
}
You can just add this line to ViewDidLoad() in swift :
self.tabBarController?.tabBar.hidden = true
I use tabBar.hidden = YES in ObjC to hide the tab bar in certain cases. I have not tried wiring it up to a tap event, though.
Code is okay but when you use presentViewController, tabBarIsVisible() is not working. To keep UITabBarController always hidden use just this part:
extension UITabBarController {
func setTabBarVisible(visible:Bool, animated:Bool) {
let frame = self.tabBar.frame
let height = frame.size.height
let offsetY = (visible ? -height : height)
UIView.animateWithDuration(animated ? 0.3 : 0.0) {
self.tabBar.frame = CGRectOffset(frame, 0, offsetY)
self.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height + offsetY)
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
}
}
}
Swift 3 version:
func setTabBarVisible(visible:Bool, animated:Bool) {
//* This cannot be called before viewDidLayoutSubviews(), because the frame is not set before this time
// bail if the current state matches the desired state
if (tabBarIsVisible() == visible) { return }
// get a frame calculation ready
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
let offsetY = (visible ? -height! : height)
// zero duration means no animation
let duration:TimeInterval = (animated ? 0.3 : 0.0)
// animate the tabBar
if frame != nil {
UIView.animate(withDuration: duration) {
self.tabBarController?.tabBar.frame = (self.tabBarController?.tabBar.frame.offsetBy(dx: 0, dy: offsetY!))!
return
}
}
}
func tabBarIsVisible() ->Bool {
return (self.tabBarController?.tabBar.frame.origin.y)! < self.view.frame.midY
}
Swift 5
To hide
override func viewWillAppear(_ animated: Bool) {
self.tabBarController?.tabBar.isHidden = true
}
To show again
override func viewDidDisappear(_ animated: Bool) {
self.tabBarController?.tabBar.isHidden = false
}
For Swift 4, and animating + hiding by placing tabBar outside the view:
if let tabBar = tabBarController?.tabBar,
let y = tabBar.frame.origin.y + tabBar.frame.height {
UIView.animate(withDuration: 0.2) {
tabBar.frame = CGRect(origin: CGPoint(x: tabBar.frame.origin.x, y: y), size: tabBar.frame.size)
}
}
To make the animations work with self.tabBarController?.tabBar.hidden = true just do this:
UIView.animateWithDuration(0.2, animations: {
self.tabBarController?.tabBar.hidden = true
})
Other than the other solution this will also work nicely with autolayout.

Resources