I am trying get this code to produce multiple "flashes" to notify the user at the end of a countdown. Right now its working great for a single flash but I ideally would like three. Ive tried duplicating the code and putting the function in a loop to no avail.
Thanks in advance for the help!
//
Flash Function
func flash() {
if let wnd = self.view{
var v = UIView(frame: wnd.bounds)
v.backgroundColor = UIColor.black
v.alpha = 1
wnd.addSubview(v)
UIView.animate(withDuration: 1.618, animations: {
v.alpha = 0.0
}, completion: {(finished:Bool) in
print("inside")
v.removeFromSuperview()
})
}
}
//
Use of flash function
if (heatUpSeconds == 0) {
heatUpTimer.invalidate()
// testing screen flashing ideas here cameraView.frame
flash()
var timer:Timer!
timer = Timer.scheduledTimer(timeInterval: delayBetweenFlashes, target: self, selector: #selector(self.flash), userInfo: nil, repeats: true)
Related
I'm using iCarousel in Swift 3 in Xcode. I'm trying to make it work like a sliding banner. I have this function:
func animate() {
DispatchQueue.global(qos: .userInteractive).async {
self.carousel.scrollToItem(at: 0, animated: false)
while(true){
sleep(3)
self.carousel.scroll(byNumberOfItems: 1, duration: 0.3)
}
}
}
I call it in viewDidLoad. It works but I think it's not as good as it could be. After I switch to another view, scrolling goes on and on. What would be the best approach to make it scroll only, when I'm seeing the view, that the carousel is in?
What solved the problem:
var timer: Timer!
self.timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(self.handleTimer), userInfo: nil, repeats: true)
func handleTimer(){
self.carousel.scroll(byNumberOfItems: 1, duration: 0.3)
}
I need infinite autoscrolling effect in .linear type in iCarousel and i already achieve autoscrolling in .cylinder type but i can't achieve this effect in .linear type.
Here is my code of achieve autoscrolling in .cylinder type
carousel.type = .linear
carousel.autoscroll = -0.4;
carousel.reloadData()
Yes, this does not work carousel.type = .linear in the case of linear so you have to make an own timer for scrolling just like that:
self.timer = NSTimer.scheduledTimerWithTimeInterval(6, target: self, selector: #selector(self.handleTimer), userInfo: nil, repeats: true)
func handleTimer(){
if itemsScroll.count != 0{
if itemsScroll.count-1 == index{
index = 0
}
else {
index += 1
}
}
let x = CGFloat(index)
if index == 0 {
carousel.scrollToOffset(x, duration: 0)
}
else {
carousel.scrollToOffset(x, duration: 2)
}
}
The index is used to get the current data for the carousel data source.
Thanks #salman for an answer with the help of Salman's answer I got a solution for an infinite solution without jerking issue when carousel type is linear
Please follow the steps given below.
1. Define timer for handle scrolling
_ = Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(self.handleTimer), userInfo: nil, repeats: true)
2. Write the delegate method of the carousel and handle wrap type with the help of wrap we solve jerk issue.
func carousel(_ carousel: iCarousel, valueFor option: iCarouselOption, withDefault value: CGFloat) -> CGFloat {
switch option {
case .wrap:
return 1
default:
return value
}
}
3. Method to handle scrolling
func handleTimer() {
var newIndex = self.carousel.currentItemIndex + 1
if newIndex > self.carousel.numberOfItems {
newIndex = 0
}
carousel.scrollToItem(at: newIndex, duration: 0.5)
}
I might be heading totally wrong direction, but what I'm trying to achieve is to load some view first and invoke some methods that will be running afterwards infinitely, while at the same time giving the User the possibility to interact with this view.
My first guess was to use viewDidApper with UIView.animate, as per below;
class MainView: UIView {
...
func animateTheme() {
//want this to print out infinitely
print("Test")
}
}
class ViewController: UIViewController {
#IBOutlet weak var mainView: MainView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 1.0, delay: 0.0, options: .repeat, animations: {
self.mainView.animateTheme()
}, completion: nil)
}
}
The case is that I can only see a single "Test" printed out but this has to be invoked infinitely. Also, as I mentioned the User needs to have the possibility to interact with the view once everything is loaded.
Thanks a lot for any of your help.
ViewDidAppear will indeed be called latest when the view is loaded, but I don´t think the user can or will have the time to interact before it´s loaded.
If you want to load animations after everything is loaded you could create a timer and wait x seconds before you do that. Below example will be called 10 seconds after initiation.
var timer = Timer()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(animateSomething), userInfo: nil, repeats: false)
}
func animateSomething() {
// Animate here
}
Update
To make an infinite loop you can use the following snippet:
UIView.animate(withDuration: 5.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.view.frame = CGRect(x: 100, y: 200, width: 200, height: 200)
}, completion: nil)
I've done something very similar on one of my ViewControllers that loads a UIWebView. While the webpage is loading I show a UILabel with text "Loading" and every 1.5 seconds I add a period to the end of the text. I think the approach you describe above and mine shown below have the same effect and neither would be considered an infinite loop.
private func showLoadingMessage(){
let label = loadingLabel ?? UILabel(frame: CGRect.zero)
label.text = "Loading"
view.addSubview(label)
loadingLabel = label
addEllipsis()
}
private func addEllipsis() {
if webpageHasLoaded { return }
guard let labelText = loadingLabel?.text else {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
self.loadingLabel?.text = labelText + "."
self.addEllipsis()
}
}
I have a button (actually two nearly identical buttons) that unhide with an action in another UIView.
The buttons hide automatically 2 seconds after the panning in the second UIView ends using dispatch_after however I would like to keep the buttons visible if either is tapped while they are visible. Here is the timer property and two methods from the UIButton subclass. I have an #IBAction that calls "justTapped" in the ViewController.
var timer = NSTimer()
func hideAndShrink(){
if !self.hidden && !timer.valid{
self.hidden = true
}
}
func justTapped(){
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "hideAndShrink", userInfo: nil, repeats: false)
}
Once either button is tapped, they will not hide.
Is the timer still valid while it calls the method in the selector?
The answer to this, thanks to luk2302, is Yes, the timer is valid when it sends the selector
How can I get around this?
As was suggested, I wrote a second method for the timer that hid the buttons without !timer.valid ?
It turned out that I did not need the second method at all as I just had any attempt to hide the buttons call justTapped instead. Here is the final code with expand and shrink animations for both buttons. self.direction is the direction I want the button to expand, from the bottom up, or the top down, and also determines the background image. I think it could have been an enumeration but I haven't figured those out yet.
func unhideAndExpand(){
if self.hidden{
expand(self.frame.size.height)
}
}
func hideAndShrink(){
if !self.hidden && !shrinking{
shrink(self.frame.size.height)
}
}
func justTapped(){
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: "hideAndShrink", userInfo: nil, repeats: false)
}
func expand(height: CGFloat){
if self.direction == "up"{
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, height/2)
} else if self.direction == "down" {
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, -height/2)
}
self.hidden = false
UIView.animateWithDuration(0.5, delay: 0, options: .CurveEaseOut, animations:
{self.transform = CGAffineTransformIdentity}, completion: nil)
}
func shrink(height: CGFloat){
self.shrinking = true
UIView.animateWithDuration(0.5, delay: 0, options: .CurveEaseOut, animations:
{if self.direction == "up"{
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, height/2)
} else if self.direction == "down" {
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, -height/2)
}
}, completion: {_ in
self.transform = CGAffineTransformIdentity
self.hidden = true
self.shrinking = false})
}
Thanks everyone for the help.
Is the timer still valid while it calls the method in the selector?
Yes, it is
The following is a complete runnable playground example demonstrating that:
import Foundation
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely()
class Bla {
var timer : NSTimer?
init() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "check", userInfo: nil, repeats: false)
NSTimer.scheduledTimerWithTimeInterval(1.1, target: self, selector: "checkAgain", userInfo: nil, repeats: false)
}
#objc func check(){
print(timer!.valid)
}
#objc func checkAgain(){
check()
}
}
let bla = Bla()
This will first output true and then output false. The timer is still valid when firing. As soon as you leave the fired method the timer gets invalidated.
That will render your if useless since the method will do nothing when fired by the timer because anything && !true evaluates to false.
What you can do to circumvent that issue is create a different method timerFired which sets some internal property of your class, e.g. var timerHasFired = false to true. Your `` then has to check that variable instead of the timer:
var timer = NSTimer()
var timerHasFired = false
func hideAndShrink(){
if !self.hidden && hideAndShrink {
self.hidden = true
}
}
func timerFired() {
timerHasFired = true
hideAndShrink()
}
func justTapped(){
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "timerFired", userInfo: nil, repeats: false)
}
Are you sure you need !timer.valid check? If hideAndShrink is called only by timer it seems you can change it to
func hideAndShrink(){
self.hidden = true
}
hideAndShrink will be called only if timer is not invalidated so you can skip this check.
I have a simple gestureRecognizer. When the gesture is "UP" then the menu slides up. "DOWN": The menu slides down. This works.
I have a timer too. It simply counts a number++ every second. This works, too. But together the timer is crashing my GUI.
The animation starts correctly, but when the timer calls my counter-method it's jumping back to the origin GUI.
What kind of problem is that? I found nothing at google, but I think I searched the wrong words. (English isn't my mothertongue and in german there isn't so much literature...)
Here are fragments of my code:
class ViewController: UIViewController, MKMapViewDelegate{
#IBOutlet weak var swipe: UILabel!
var number: Int = 0
#IBOutlet weak var menuView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
var topSwipe = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipes:"))
var downSwipe = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipes:"))
topSwipe.direction = .Up
downSwipe.direction = .Down
view.addGestureRecognizer(topSwipe)
view.addGestureRecognizer(downSwipe)
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "counter", userInfo: nil, repeats: true)
}
func counter() {
number++
swipe.text = "\(number)"
}
func handleSwipes(sender:UISwipeGestureRecognizer) {
if (sender.direction == .Up) {
print("TOP")
var menuPosition = CGPointMake(self.menuView.frame.origin.x, self.menuView.frame.origin.y - 100.0)
UIView.animateWithDuration(0.65, animations: {
self.menuView.frame = CGRectMake(menuPosition.x,menuPosition.y,self.menuView.frame.size.width,self.menuView.frame.size.height)
})
}
if (sender.direction == .Down) {
menuChoose = true
UIView.animateWithDuration(0.65, animations: {
var menuPosition = CGPointMake(self.menuView.frame.origin.x, self.menuView.frame.origin.y + 100.0)
self.menuView.frame = CGRectMake(menuPosition.x,menuPosition.y,self.menuView.frame.size.width,self.menuView.frame.size.height)
})
}
}
}
I tried to lock the timer. So when the gesture begins, then just count the number without swipe.text = "(number)"
The animation goes to end, but after an "unlock" the GUI jumped back to the origin.
Thank u for some codesnippets or keywords that I can search :)
It seems that whenever a UILabel's text property is set, it redraws the view, resulting in resetting menuView's position (perhaps someone can elaborate on the "why" in a comment). If you use auto layout constraints to manipulate the position of menuView, it works. You can do this by setting spaceConstraint.constant += 100 or spaceConstraint.constant -= 100 and then calling
UIView.animateWithDuration(0.65, animations: {
self.view.layoutIfNeeded()
})