I'm trying to create an app that is similar to SnapChat when it takes videos. You are supposed to hold down a button to start taking a video and then release it when you are done. I'm trying to implement a timeout feature after 7 seconds and also a progress bar by using a Timer object. However, when I try fire the timer, it only calls the given selector once and that's it even though I tell it to repeat itself.
Here's my code to initialize the button:
let photoButton:UIButton = {
let but = UIButton(type: .custom)
but.layer.cornerRadius = 40
but.layer.borderColor = UIColor.white.cgColor
but.layer.borderWidth = 4
but.clipsToBounds = true
but.addTarget(self, action: #selector(takeVideo), for: .touchDown)
but.addTarget(self, action: #selector(stopVideo), for: [.touchUpInside, .touchUpOutside])
but.translatesAutoresizingMaskIntoConstraints = false
return but
}()
Here's the function takeVideo that is called when the user starts to hold down the button. I initialize and fire the Timer object here:
#objc func takeVideo() {
progressBar.isHidden = false
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateProgressbar), userInfo: nil, repeats: true)
startTime = Date().timeIntervalSince1970()
timer?.fire()
}
This is the function stopVideo where I invalidate the Timer object:
#objc func stopVideo() {
timer?.invalidate()
// stop video
}
And this is the updateProgressBar function:
#objc private func updateProgressbar() {
let maxLength = 7.0
let difference = Date().timeIntervalSince1970 - startTime!
progressBar.progress = Float(difference / maxLength)
if difference >= maxLength {
stopVideo() // Invalidates the timer and will stop video.
}
}
It seems that this person has had a similar issue, but when I tried to incorporate his answer of presenting the current view controller using async, it still doesn't work. Here's how I present the video recording view controller:
let recorder = RecorderViewController()
recorder.db = db
DispatchQueue.main.async(execute: {
self.present(recorder, animated: true, completion: nil)
})
Edit: I've fixed the incorrectness concerning the Timer object's fireDate property (the fix is also fixed in the above code). It fixed my problem.
Related
I implemented a SnackBar / Toast Message in Swift 4 which allows the developer to set the following display duration (enum) and with the following behaviour:
.long / .short: Timer gets fired and dismisses the SnackBar OR the user taps the view and it also dismisses
.constant: No Timer, only TapGesture to dismiss
Now in the first case (.long / .short) the SnackBar reacts as expected. Either the Timer fires or the user taps and the view gets dismissed.
In the second case (.constant) - where there is no timer - the Tap gesture is not recognised and the view doesn't get dismissed
here are the three functions:
private func defineDismissMode(){
switch self.message.duration {
case .short, .long:
self.setTimer()
case .constant:
self.setTapGesture()
}
}
private func setTimer(){
guard self.timer == nil else {return}
self.timer = Timer.scheduledTimer(timeInterval: self.message.duration.rawValue, target: self, selector: #selector(self.dismissSnackBar), userInfo: nil, repeats: false)
self.setTapGesture()
}
private func setTapGesture() {
self.tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissSnackBar))
self.snackView.addGestureRecognizer(self.tapGestureRecognizer!)
}
#objc private func dismissSnackBar(){
...
}
Anyone a clue why when setTapGesture() is called after setTimer() it works and a tap triggers self.dismissSnackBar , but when called without setTimer() the UITapGestureRecognizer is not triggered?
Note: Yes I checked that case: .constant -> setTapGesture() gets called
I'm a bit stumped on this :
Context
I have a 'workout session' which is in a tableView inside a UIViewController. Each cell has an exercise name, number of reps and a swap button. The swap button generates a new exercise from the exercise bank (i.e. if it says "bench press" but you don't have a bench press then you can swap it for something else).
Problem
My swap button code works and a new exercise populates the cell. However, when I press "Play" on my workout timer, it resets to what it was before!
Code - swap button
func swapButtonTapped(cell: WorkoutCell) {
let realmExercisePool = realm.objects(ExerciseGeneratorObject.self)
let index = Int(arc4random_uniform(UInt32(realmExercisePool.count)))
let newExercise = realmExercisePool[index].generateExercise()
cell.exerciseName.text = newExercise.name
cell.repsNumber.text = String(newExercise.reps)
}
Code - Timer
#IBAction func timerControlTapped(_ sender: UIButton) {
if isTimerRunning == false {
sender.setImage(#imageLiteral(resourceName: "timerPause"), for: UIControlState.normal)
runTimer()
isTimerRunning = true
swapButtonEnabled = false
workoutTableView.reloadData()
} else {
sender.setImage(#imageLiteral(resourceName: "timerPlay"), for: UIControlState.normal)
workoutTimerObject.invalidate()
isTimerRunning = false
}
}
Code - Run Timer
func runTimer() {
workoutTimerObject = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(WorkoutViewController.workoutTimer), userInfo: nil, repeats: true)
isTimerRunning = true
}
The original group of exercises are passed in an array of workoutExercises from the previous VC (a 'workout set up screen')
Thanks everyone!
For anyone else who has a similar problem, I managed to work this out.
This was happening because I was simply changing the 'content' of the cell in my swapButtonTapped and not the actual data source.
When I then called "reloadData()" in timerControlTapped, it was reloading the original data.
What I needed to do was replace the actual data by replacing the exercise at the selected index with a new one in the "selectedWorkoutExerciseArray".
I used this code :
func swapButtonTapped(cell: WorkoutCell) {
let myIndexPath = workoutTableView.indexPath(for: cell)
let realmExercisePool = realm.objects(ExerciseGeneratorObject.self)
let index = Int(arc4random_uniform(UInt32(realmExercisePool.count)))
let newExercise = realmExercisePool[index].generateExercise()
selectedWorkoutExerciseArray[(myIndexPath?.row)!] = newExercise
cell.exerciseName.text = newExercise.name
cell.repsNumber.text = String(newExercise.reps)
}
backround.backgroundColor = UIColor.redColor()
//Delay code ?
backround.backgroundColor = UIColor.blueColor()
Is there anyway to do this? I do not know the exact code line to create this type of delay.
func performDelayBlock(block:() -> Void, afterDelay delay:NSTimeInterval)
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), block)
}
usage:
backround.backgroundColor = UIColor.redColor()
performDelayBlock({
self.backround.backgroundColor = UIColor.blueColor()
}, afterDelay: 1)
You can do it using NSTimer.
//Register for NSTimer wherever you are setting your first color and then set the interval time.
//Since you want to repeat you can just do repeat to be true
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "changeBackground", userInfo: nil, repeats: true)
func changeBackground() {
backround.backgroundColor = UIColor.redColor() //whichever you like
//Have conditional code to change it back and forth to red and other color you want to.
}
In my app I have a view, and I want this view to be dismissed everytime it is shown after 8 seconds. In order to do that I created a timer inside view creation function that hides view after firing. It works. But in some cases I want to present two copies of the view I want both be dismissed after time it was created +8 seconds. Issue is that timer works only once - it hides one copy of view, and second remains on screen
My code is
func presentShowStatusShort(text text:String) {
pointsShort = NSBundle.mainBundle().loadNibNamed("helperShowStatusShort", owner: self, options: nil).first! as! helperShowStatusShort
pointsShort?.frame.size.width = self.view.bounds.width
if pointsShortOnScreen {
pointsShort!.frame.origin.y = pointsShort!.frame.origin.y + 39
}
pointsShort?.text.text = "\(text.uppercaseString)"
pointsShort!.dismissButton.addTarget(self, action: "dismissShowStatusShort", forControlEvents: UIControlEvents.TouchUpInside)
var timer = NSTimer.scheduledTimerWithTimeInterval(8, target: self, selector: "dismissShowStatusShort", userInfo: nil, repeats: false)
pointsShortOnScreen = true
view.addSubview(pointsShort!)
}
func dismissShowStatusShort() {
pointsShortOnScreen = false
UIView.animateWithDuration(0.5, animations: {
self.pointsShort?.frame.origin.y = (self.pointsShort?.frame.origin.y)! - 64
}, completion: {
finished in
self.pointsShort?.removeFromSuperview()
})
}
From an OOP point of view you should create a class to help you with this. The class would own the timer and maintain a reference to the view. When the timer expires it calls a callback, passing itself as the parameter, so the controller can get the associated view and dismiss it.
All of the instances of this class would be stored in an array instance variable on the controller so you can add multiple and remove each one once it expires.
I got a button with a LongPressureGesture, and i would like to have a small ProgressView on top of this button as visual feedback for the user that the longPressureGesture is recognized.
I'm stuck on how to detect the beginning of the longPressure and the duration of the longPressure to be able to set the setProgress() on my ProgressView.
EDIT: So i inspired myself from the answers, thank you. Here is what i made. Feel free to comment the following code, maybe there is a better solution.
private var lpProgress:Float = 0
private var startTouch: NSTimer!
#IBAction func pauseJogButtonTouchDown(sender: AnyObject) {
self.startTouch = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "updateLPGestureProgressView", userInfo: nil, repeats: true)
}
func updateLPGestureProgressView() {
self.lpProgress += 0.1
self.lpGestureProgressView.setProgress(self.lpProgress, animated: true)
if self.lpProgress >= 1 {
self.startTouch.invalidate()
self.pauseBarButton.hidden = true
self.lpGestureProgressView.setProgress(0.0, animated: false)
self.toolbarHomeMadeView.hidden = false
self.switchToState(.Paused)
}
}
#IBAction func pauseJogButtonTouchUpInside(sender: AnyObject) {
self.lpProgress = 0
self.startTouch.invalidate()
self.lpGestureProgressView.setProgress(0.0, animated: false)
}
You do not need the LongPressureGesture in this case.
Use "Touch Down" IBAction of UIButton to start NSTimer, and "Touch Up Inside" to stop timer and check if the delay was right.
ProgressView you can fill by timer progress.
Set up an NSTimer on touchesBegan.
At the same time start your animation to animate the view.
When touchesEnded is triggered then stop the animation if the NSTimer has not triggered yet and cancel the timer.
When the timer finishes run your desired action.
Long Press isn't really designed for this sort of thing.