Extracting value from delegated function Swift 4 - ios

I need help concerning the logic behind a problem I have regarding delegate and protocol. I made a timer app that allows you set a timer. The timer selection is within SettingsController and I used protocols and delegates to pass data into my TimerController. This works as the timer change. But I am having problems concerning creating the logic for the reset button. I want the reset button to have the same data. Example, if I choose 5mins as the timer, the reset button will reset as 5min. Currently its reseting to 0. The problem is really extracting the data from the delegated function into the TimerController so I can change the value of counter which is nil.
SettingsController
import UIKit
protocol SettingsVCDelegate {
func didSelectReadingSpeed(counter: Int)
}
class SettingsVC: UIViewController {
var counter = Int()
var settingsDelegate: SettingsVCDelegate!
#IBAction func changeReadingSpeed(_ sender: UISlider) {
speedSlider.value = roundf(speedSlider.value)
if speedSlider.value == 1 {
speedLabel.text = "1 min (60 pgs / hr) Extreme"
counter = 1
print(counter)
} else if speedSlider.value == 2 {
speedLabel.text = "2 min (30 pgs / hr) Fast"
counter = 2
}
}
#IBAction func didTappedStart(_ sender: Any) {
settingsDelegate.didSelectReadingSpeed(counter: counter)
dismiss(animated: true, completion: nil)
}
TimerController
class TimerController: UIViewController, CountdownTimerDelegate, SettingsVCDelegate {
lazy var countdownTimer: CountdownTimer = {
let countdownTimer = CountdownTimer()
return countdownTimer
}()
var resetBtn: UIButton = {
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(resetBtnDidTouch), for: .touchUpInside)
return button
}()
#objc func resetBtnDidTouch() {
countdownTimer.reset()
countdownTimer.setMinuteTimer(minutes: 0, seconds: counter)
countdownTimer.setHourTimer(hour: 0, minutes: 0, seconds: duration)
}
var counter = Int()
var isRunning = false
var resumeTapped = false
//MARK: - ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
countdownTimer.delegate = self
countdownTimer.setMinuteTimer(minutes: 0, seconds: counter)
countdownTimer.setHourTimer(hour: 0, minutes: 0, seconds: duration)
}
//MARK: - Settings Delegate
func didSelectReadingSpeed(counter: Int) {
let counter = counter
countdownTimer.setMinuteTimer(minutes: 0, seconds: counter)
}
Very much appreciate if someone can help me.

You are using three(!) counter variables in your code
The property on the top level
The parameter in didSelectReadingSpeed
The local variable in didSelectReadingSpeed
This is pretty confusing because all three objects are different. To avoid the naming confusion name the parameter in the delegate method speed (you could even omit Speed in the method name) for example
protocol SettingsVCDelegate {
func didSelectReading(speed: Int)
}
The issue occurs because the property whose value is used to reset the timer never changes.
Solution: When the delegate method is called delete the local variable counter and assign speed to the property counter.
func didSelectReading(speed: Int) {
counter = speed
countdownTimer.setMinuteTimer(minutes: 0, seconds: counter)
}
Two notes:
Don't initialize a scalar property with the default initializer e.g. Int(), assign a default value which is more descriptive:
var counter = 0.
Initializing countdownTimer with a closure looks pretty cool but it's pointless if the property is only initialized.
lazy var countdownTimer = CountdownTimer() does the same thing.

I agree with the previous answer. Additionally, you declared and initialized "counter" as a minute.
countdownTimer.setMinuteTimer(minutes: counter, seconds: 0) should be in this way or if "counter" declared as a seconds:
minutes = counter / 60
seconds = counter % 60

Related

Is there a way to programmatically associate a variable and a target object with the control in swift?

I'm creating a slider programmatically due to and Xcode bug, (don't let me center the thumb when I change the slider values, so I decided to do it using code) and I want to have a variable which saves the slider value. Is there a way to associate the variable and the target object with the control, similar to "addTarget", but instead of an action, its a variable?
I don't know if I explained myself but tell me if I need to be more specific. Thanks in advance :) for helping me.
This is my code:
import UIKit
class ViewController: UIViewController {
var slider: UISlider!
#IBOutlet weak var sliderVar: UISlider!
var currentSliderValue = 0
override func viewDidLoad() {
super.viewDidLoad()
slider = UISlider(frame: CGRect(x: 98, y: 173, width: 699, height: 30))
slider.center = self.view.center
slider.minimumValue = 1
slider.maximumValue = 100
slider.value = 50
slider.isContinuous = true
slider.addTarget(self, action: #selector(sliderMoved(_:)), for: UIControl.Event.valueChanged)
self.view.addSubview(slider)
}
#IBAction func sliderMoved(_ sender: UISlider) {
currentSliderValue = lroundf(sender.value)
}
}
My function “sliderMoved” changes the sliderCurrentValue variable, but this var won’t change until I use the slider and move it. I also have a button there, that when you touch it up it shows the slider value, but the “sliderCurrentValue” only changes its value when the slider is moved. I was thinking of creating an IBOutlet but I don’t know how to connect this one with the slider.
As far as I understand you need to bind variable to slider's value. There are several ways to achieve it. There is one way. Declare variable with custom setters and getters
class ViewController: UIViewController {
var slider: UISlider!
var sliderValue: Float {
set {
// use optional chaining here helps avoid crashes
slider?.setValue(newValue, animated: true)
}
get {
// if slider control hasn't created yet you need to return some dummy value
slider?.value ?? -1
}
}
func viewDidLoad() {
// your current implementation here
}
}

How to detect number of taps

I want to detect number of taps
User can tap multiple times and I have to perform action based on number of taps.
I tried using UIButton with the below code but its detecting all the taps
if I tap three times, It prints
1
2
3
Code -
tapButton.addTarget(self, action: #selector(multipleTap(_:event:)), for: UIControl.Event.touchDownRepeat)
#objc func multipleTap(_ sender: UIButton, event: UIEvent) {
let touch: UITouch = event.allTouches!.first!
print(touch.tapCount)
}
I need the output to be just 3 if i tap three times.
Edit 1: for ex - if you tap three times in youtube it will forward 30 seconds and if you tap 4 times it will forward 40 secs.
You aren’t defining the problem clearly. Are you saying that you want to detect a group of repeated taps within a short time as a single, multi-tap event, and report the number of taps? If so, you need to add logic to do that. Decide how long to wait between taps before you consider the event complete.
You’ll then need to keep track of how many taps have occurred, and how long it’s been since the last tap. You’ll need a timer that fires when the inter-tap interval has passed with no new taps so that you can consider the event complete.
All this sounds a lot more like a tap gesture recognizer than a button. Tap gesture recoginizers are written to detect a specific number of taps, with timeouts and such, but don’t respond to variable numbers of taps. You might want to create a custom gesture recognizer that responds to a variable number of taps instead of a button.
introduce var to hold tap count
your modified code will be:
tapButton.addTarget(self, action: #selector(singleTap(_:)), for: .touchUpInside)
private var numberOfTaps = 0
private var lastTapDate: Date?
#objc private func singleTap(_ sender: UIButton) {
if let lastTapDate = lastTapDate, Date().timeIntervalSince(lastTapDate) <= 1 { // less then a second
numberOfTaps += 1
} else {
numberOfTaps = 0
}
lastTapDate = Date()
if numberOfTaps == 3 {
// do your rewind stuff here 30 sec
numberOfTaps = 0
}
}
edit: I'm not mind reader, but I guess you look for something like above (updated code)
class ViewController: UIViewController {
var tapCount: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
tapButton.addTarget(self, action: #selector(multipleTap(sender:)), for: .touchUpInside)
}
#objc func multipleTap(sender: UIButton) {
tapCount += 1
if tapCount == 3 {
print(tapCount) //3
}
}
}
I'm not sure for what exactly you need this, but below Duncan's answer I read that you need to copy something from YouTube logic.
Yes, of course you can use tap gesture recognizer, but if you need from some reason UIButton, you can create its subclass.
After first touch nothing happens, but then after every next touch valueChanged handler which you will set in view controller gets called. If you don't press button again to certain wait duration, touches will be reset to 0.
class TouchableButton: UIButton {
var waitDuration: Double = 1.5 // change this for custom duration to reset
var valueChanged: (() -> Void)? // set this to handle press of button
var minimumTouches: Int = 2 // set this to change number of minimum presses
override init(frame: CGRect) {
super.init(frame: frame)
setTarget()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setTarget()
}
private func setTarget() {
addTarget(self, action: #selector(buttonTouched), for: .touchUpInside)
}
#objc private func buttonTouched() {
touches += 1
}
private var timer: Timer?
private var touches: Int = 0 {
didSet {
if touches >= minimumTouches {
valueChanged?()
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: waitDuration, repeats: false) { _ in
self.touches = 0
}
}
}
}
}
then when you need to set what should happen after every touch, you can set value changed handler
button.valueChanged = { // button of type `TouchableButton`
//print("touched")
... // move 10s forward or backwards
}
you can also change waitDuration property which specify wait time between last press and time when touches will be reset
button.waitDuration = 1
also you can set number of minimum touches (first time when valueChanged gets executed)
button.minimumTouches = 3
You need to check time difference for touch events.
For ex: If button is pressed 4 times and in particular time, say 1 sec, if it does not get pressed again then you can print 4 taps otherwise don't print.
For this you also need to call the timer so that you can check the time of last event and print accordingly.
var timer : Timer?
var timeDuration = 2.0
var tapCount = 0
var lastButtonPressedTime : Date?
#IBAction func tapCountButtonAction(_ sender: UIButton) {
lastButtonPressedTime = Date()
if(tapCount == 0){
tapCount = 1
timer = Timer.scheduledTimer(withTimeInterval: timeDuration, repeats: true, block: { (timer) in
let difference = Calendar.current.dateComponents([.second], from:self.lastButtonPressedTime!, to:Date())
if(difference.second! > 1){
print(self.tapCount)
timer.invalidate()
self.tapCount = 0
}
})
}else{
tapCount += 1
}
}
The technique used here is introducing a time delay in executing the
final action
static let tapCount = 0
tapButton.addTarget(self, action: #selector(multipleTap(_:event:)), for: UIControl.Event.touchDownRepeat)
// An average user can tap about 7 times in 1 second using 1 finger
DispatchQueue.main.asyncAfter(deadLine: .now() + 1.0 /* We're waiting for a second till executing the target method to observe the total no of taps */ ) {
self.performAction(for: tapCount)
}
#objc func multipleTap(_ sender: UIButton, event: UIEvent) {
tapCount += 1
}
/// Perform the respective action based on the taps
private func performAction(for count: Int) {
// Resetting the taps after 1 second
tapCount = 0
switch (count){
case 2:
// Handle 2 clicks
case 3:
// Handle 3 clicks
case 4:
// Handle 4 clicks
default:
print("Action undefined for count: \(count)")
}
}

How to create an If statement that will change the alpha of a label depending on the number of a variable I have created?

I have set a label's alpha to 0 in viewDidLoad:
I want the label's alpha to change to 1 once the variable 'count' has reached 8.
override func viewDidLoad() {
super.viewDidLoad()
messageFour.alpha = 0
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if Count == 8 {
self.label.alpha = 1
}
}
The count works fine, but when it reaches 8 the label is not showing!
Any help is appreciated!
You have only called it once in your viewDidAppear. You need to check this condition each time count is incremented.
You can move complete code of your if-else in separate method. Call that method in your viewDidAppear as well as when your count values gets incremented.
As #V.Kambhir suugessted you can use property observers didSet or willSet as per your requirement.
You can use didSet method.
Example:
var count = 0 {
didSet {
if count == 8 {
self.label.alpha = 1
}
}
}
Apple documentation:
didSet is called immediately after the new value is stored.
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html

Swift 2 - Print New Value from Slider at Regular Interval

I am trying to print a value from a slider at regular intervals. But only print the value if it is different to that last printed. I also do not want to miss any of the output values from the slider.
To do this I have created an array and added an element to the start of that array if it is different to the one already at the start. I have then used a repeating NSTimer to regularly call a function that prints the last element in the array before removing it from the array.
What happens when I run the app is the NSTimer stops anything being printed for it's set time, but then all of the elements print at once and more than one of each print. I've tried messing about with lots of different things - this is the closest I have got to making it work.
If you need to know any more info let me know.
I really appreciate any help given, thanks very much.
var sliderArray: [Float] = []
var timer: NSTimer!
let step: Float = 1
#IBAction func sliderValueChanged(sender: AnyObject)
{
let roundedValue = round(slider.value / step) * step
slider.value = roundedValue
if sliderArray.first != slider.value
{
sliderArray.insert(slider.value, atIndex: 0)
}
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(sendSliderPosition), userInfo: nil, repeats: true)
}
func sendSliderPosition()
{
if sliderArray.count > 0
{
print(self.sliderArray.last)
sliderArray.removeLast()
}
}
I would suggest using CADisplayLink. A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display. Which is perfect for your slider case.
This will also not trigger unnecessary call when the slider or the UI is at rest.
class C: UIViewController {
var displayLinkTimer: CADisplayLink?
#IBOutlet weak var slider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
displayLinkTimer = CADisplayLink(target: self, selector: #selector(printSliderValue))
let runLoop = NSRunLoop.mainRunLoop()
displayLinkTimer?.addToRunLoop(runLoop, forMode: runLoop.currentMode ?? NSDefaultRunLoopMode )
displayLinkTimer?.paused = true
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
displayLinkTimer?.paused = true
}
deinit {
displayLinkTimer?.invalidate()
}
func printSliderValue()
{
let step: Float = 1
let roundedValue = round(slider.value / step) * step
slider.value = roundedValue
print(roundedValue)
}
}
The basic idea is this:
--> Every time the screen needs to redraw (this will happen at max around 60 frames per second taking into consideration this is fps rate), we get a chance to perform function.
--> to do so, we add the displayLink to the Run Loop. (Run lopp processes input/ refreshes UI and time slices)
--> NOTE This method wont be called if there is no redraw needed on the screen. This is not a timer per say that fires periodically. It fires when redraw is needed. In Sliders case, we want this to fire when we move slightest of the slider too.
For more info on how it actually works try it out and see the apple documentation. Make sure to invalidate before deinitializing the ViewController.
Figured out the answer, thank to everyone for the help and suggestions:
override func viewDidLoad()
{
super.viewDidLoad()
NSTimer.scheduledTimerWithTimeInterval(0.02, target: self, selector: #selector(sendSliderPosition), userInfo: nil, repeats: true)
}
#IBAction func sliderValueChanged(sender: AnyObject)
{
let step: Float = 1
let roundedValue = round(slider.value / step) * step
slider.value = roundedValue
if sliderArray.first != slider.value
{
sliderArray.insert(slider.value, atIndex: 0)
}
}
func sendSliderPosition()
{
if sliderArray.count > 1
{
let end1 = sliderArray.count-2
print(sliderArray[end1])
sliderArray.removeLast()
}
}
Explanation:
If the new slider value is different to the one already in the array then add it to the array at the start. Use an NSTimer to repeatedly call the sendSliderPosition function from viewDidLoad. The function will only be performed if there is more than one element in the array. If there is, print the element before the last one and remove the last. This always ensures that there is one element in the array so the function does not always run and that the element printed is the most recent one that hasn't already been printed.

Button in Xcode doesn't change a variable - Swift

I am trying to change the value of a variable in Xcode 7 with Swift. For some strange reason, it just doesn't work.
Here are the functions I used.
#IBAction func ten(sender: AnyObject) {
var percentage = 0.10
print("test")
}
#IBAction func twentyBtn(sender: AnyObject) {
var percentage = 0.20
}
#IBAction func fifteenBtn(sender: AnyObject) {
var percentage = 0.15
}
#IBAction func twentyfiveBtn(sender: AnyObject) {
var percentage = 0.25
}
#IBAction func thirtyBtn(sender: AnyObject) {
var percentage = 0.30
}
Then later in the code I use the percentage variable in this function.
#IBAction func calcTip(sender: AnyObject) {
var billAmount: Int? {
return Int(billField.text!)
}
var tipTotal = percentage * Double((billAmount)!)
toalLabel.text = String(tipTotal)
testLabel.text = String(percentage)
}
I don't understand why this shouldn't work because I also have this variable underneath my outlets where at the beggining of the code.
What's even more confusing is that if I try to see if it the button would print into the output, as you see I've tried for the one of the buttons, IT WORKS. Even trying to change the text within a label works, but for some strange reason it won't change a variable.
I'm not sure if this could be a bug with Xcode either.
Declare and initialize your percentage variable outside of your IBAction functions.
Like Mukesh already explained in the comments, if you are writing var percentage = 0.10 within one of these functions, you are not assigning 0.10 to the correct variable. Instead, you are creating a new variable within the scope of that function.
Any code written outside of that function cannot access it, and unless you are returning this value or passing it to a different function, the variable will be deleted by the garbage collector after the program finishes executing that function (so pretty much immediately).
Long story short, your code should look something like this instead:
var percentage = 0;
#IBAction func ten(sender: AnyObject) {
percentage = 0.10
print("test")
}
#IBAction func calcTip(sender: AnyObject) {
var billAmount: Int? {
return Int(billField.text!)
}
var tipTotal = percentage * Double((billAmount)!)
toalLabel.text = String(tipTotal)
testLabel.text = String(percentage)
}

Resources