How to customize UISlider Value in Swift - ios

I create UISlider Programmatically. I try to customise UISlider value as 4.1, 4.2, 4.3, 4.4, 4.5, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 6.0, ... Anybody give me the solution
sliderDemo = UISlider(frame:CGRectMake(0, 0, 200,20))
var numberOfSteps : NSInteger = numbers.count - 1
sliderDemo.minimumValue = 6.5
sliderDemo.maximumValue = 4.1
sliderDemo.continuous = true
sliderDemo.value = 4.0
sliderDemo.addTarget(self, action: "sliderValueDidChange:", forControlEvents: .ValueChanged)
self.view.addSubview(sliderDemo)
func sliderValueDidChange(sender:UISlider!)
{
println("number:\(sender.value)")
}

I just create an example of a custom slider for you, in this case I am creating and adding it myself to the form but you can easily adapt to use the one from storyboard, all you need to do is to add your values to the numbers array, the result will be in the variable number in the valueChanged function, you can use observer, notifications or protocol to retrieve the value as it change, or simply call a function from there.
class ViewController: UIViewController {
var slider:UISlider?
// These number values represent each slider position
var numbers = [1, 2, 3, 4, 5, 6, 7] //Add your values here
var oldIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
slider = UISlider(frame: self.view.bounds)
self.view.addSubview(slider!)
// slider values go from 0 to the number of values in your numbers array
var numberOfSteps = Float(numbers.count - 1)
slider!.maximumValue = numberOfSteps;
slider!.minimumValue = 0;
// As the slider moves it will continously call the -valueChanged:
slider!.continuous = true; // false makes it call only once you let go
slider!.addTarget(self, action: "valueChanged:", forControlEvents: .ValueChanged)
}
func valueChanged(sender: UISlider) {
// round the slider position to the nearest index of the numbers array
var index = (Int)(slider!.value + 0.5);
slider?.setValue(Float(index), animated: false)
var number = numbers[index]; // <-- This numeric value you want
if oldIndex != index{
println("sliderIndex:\(index)")
println("number: \(number)")
oldIndex = index
}
}
}
I hope that helps you!

Related

Extracting value from delegated function Swift 4

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

programmatically have slider set label with in a uitableview cell

Im trying to update my label when the slider moves though I may be missing something? perhaps the slider is not connected to the function?
let slider: UISlider = {
let s = UISlider()
s.minimumValue = 0
s.maximumValue = 1000
s.isContinuous = true
s.tintColor = UIColor.blue
s.value = 500
s.addTarget(self, action: #selector(paybackSliderValueDidChange),for: .valueChanged)
return s
}()
func paybackSliderValueDidChange(sender: UISlider!)
{
print("payback value: \(sender.value)")
sliderLabel.text = "\(sender.value)"
}
Make sure to mark your paybackSliderValueDidChange(sender:) method as #objc. Otherwise, UIKit won't be able to access it in Swift 4
just had to change "let slider: UISlider" to "lazy var slider: UISlider"
and voila

Swift Array Append Overwriting Other Array Values

I'm new to Swift, but I'm not new to coding. I thought I would try my hand at making a little game. It's something I like to do when learning.
Here's what I'm doing. I start of by calling a method that initialized my "object templates"
internal func initializeObjectTemplates(){
objectTemplates.append(GameObject(passed_x: 0,
passed_y: 0,
passed_max: 25,
passed_min: 25,
passed_points: 100,
passed_img: "BeerGlass1",
passedLengthOfTimeOnScreen: 5,
passedBottomChance: 1,
passedTopChance: 50,
passedId: 0))
objectTemplates.append(GameObject(passed_x: 0,
passed_y: 0,
passed_max: 25,
passed_min: 25,
passed_points: 300,
passed_img: "BeerGlass2",
passedLengthOfTimeOnScreen: 2,
passedBottomChance: 51,
passedTopChance: 100,
passedId: 0))
}
I have a timer that runs a func named "update" every second. The "update" func randomly selects one of the two templates, adds some other information to the template object, does some other logic, and appends the object to an array I have. Everything up to the append seems to be working. I have added various breakpoints and the GameObject object seems to be getting populated correctly.
When I append the object it is overwriting other items in the objects array. I can't figure it out. I've googled it as much as possible, and can't really seem to find anything that fits my issue. Here's the rest of my code related to this.
This is the top of the ViewController and the viewDidLoad
internal var step = 0
internal var objects = [GameObject]()
#IBOutlet weak var points: UILabel!
internal var objectTemplates = [GameObject]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = UIColor.blackColor()
initializeObjectTemplates()
let timer = NSTimer(timeInterval: 1, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
//var i = 1
//while i <= 100{
// update()
// i += 1
//}
}
Update Func - the commented out/hard coded append works, but obviously not what I want
internal func update(){
step += 1
//removeExpiredButtons()
objects.append(populateObjectTemplate(selectObjectTemplate(), step: step))
//objects.append(GameObject(passed_x: 0,
// passed_y: 0,
// passed_max: 25,
// passed_min: 25,
// passed_points: 300,
// passed_img: "BeerGlass2",
// passedLengthOfTimeOnScreen: 2,
// passedBottomChance: 51,
// passedTopChance: 100,
// passedId: step))
print("There are \(objects.count) items and the first item has an id of \(objects[0].id)")
}
These are the methods that update calls
selectObjectTemplate
func selectObjectTemplate() -> GameObject {
let rand = Random.within(1...100)
return objectTemplates.filter(){
let isAbove = $0.bottomChance <= rand
let isBelow = $0.topChance >= rand
return isAbove && isBelow
}[0]
}
populateObjectTemplate
func populateObjectTemplate(obj: GameObject, step: Int) -> GameObject {
let widthBoundary = Float(self.view.frame.width - 20)
let heightBoundary = Float(self.view.frame.height - 20)
let placex = CGFloat(Random.within(20.0...widthBoundary))
obj.x = placex
let placey = CGFloat(Random.within(50.0...heightBoundary))
obj.y = placey
obj.expiration = obj.lengthOfTimeOnScreen + step
obj.id = step
obj.button = populateObjectButton(obj)
return obj
}
populateObjectButton
func populateObjectButton(obj: GameObject) -> UIButton {
let button = UIButton(type: UIButtonType.Custom)
let image = UIImage(named: obj.img)
button.setBackgroundImage(image, forState: UIControlState.Normal)
button.frame = CGRectMake(obj.x, obj.y, obj.minSize, obj.minSize)
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.layer.masksToBounds = true
button.addTarget(self, action: #selector(ViewController.objectTapped(_:)), forControlEvents: .TouchUpInside)
button.tag = obj.id
self.view.addSubview(button)
return button
}
Sorry for such a lengthy post. I just wanted to include as much information as possible. I don't usually post to places like this, because I try as hard as possible to find the solution myself. The swift file is also on Git.
https://github.com/JoeBrewing/TapGlassMasterChallenge/blob/master/Glass%20Tap%20Master%20Challenge/ViewController.swift
Your objectTemplates array stores references to two GameObject instances that were created in initializeObjectTemplates. When your timer fires you select one of these 'template' objects, modify it and add it to the array. However, the array is merely storing references to the objects you add to it, and you only have two objects, even though you are adding those objects to the array multiple times.
When you call populateObjectTemplate you modify one of the two object templates, which means that all other entries in the array that refer to that particular template will reflect those modifications. You need populateObjectTemplate to return a new GameObject, using the information in the template;
func populateObjectTemplate(obj: GameObject, step: Int) -> GameObject {
let newGameObject=GameObject(passed_x: 0,
passed_y: obj.passed_y,
passed_max: obj.passed_max,
passed_min: obj.passed_min,
passed_points: obj.passed_points,
passed_img: obj.passed_img,
passedLengthOfTimeOnScreen: obj.passedLengthOfTimeOnScreen,
passedBottomChance: obj.passedBottomChance,
passedTopChance: obj.passedTopChance,
passedId: obj.passedId)
let widthBoundary = Float(self.view.frame.width - 20)
let heightBoundary = Float(self.view.frame.height - 20)
let placex = CGFloat(Random.within(20.0...widthBoundary))
newGameObject.x = placex
let placey = CGFloat(Random.within(50.0...heightBoundary))
newGameObject.y = placey
newGameObject.expiration = obj.lengthOfTimeOnScreen + step
newGameObject.id = step
newGameObject.button = populateObjectButton(obj)
return newGameObject
}

How do you setup a loop with an interval/delay?

I'm trying to make a Wack-a-Mole game. The way I am doing so is by having one button randomly appear and disappear around the screen after someone has tapped it. If they do not tap the button within one second after it reappears, then it will disappear and find a new position, then reappear and wait one second and repeat the above steps. However, whenever I run this code, it doesn't do that. It moves positions only if I get rid of the 'while' statement and the 'if else' statement. Why won't it loop, disappear, reappear, etc?
Delay is this: https://stackoverflow.com/a/24318861/5799228
#IBAction func moveButton(button: UIButton) {
while self.WaffleButton.hidden == true || false {
if self.WaffleButton.hidden == false {
self.WaffleButton.hidden = true
delay(3) {
// Find the button's width and height
let buttonWidth = button.frame.width
let buttonHeight = button.frame.height
// Find the width and height of the enclosing view
let viewWidth = button.superview!.bounds.width
let viewHeight = button.superview!.bounds.height
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - buttonWidth
let yheight = viewHeight - buttonHeight
// 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.
button.center.x = xoffset + buttonWidth / 2
button.center.y = yoffset + buttonHeight / 2
self.WaffleButton.hidden = false
self.delay(1) {
self.WaffleButton.hidden = true
}
}
} else { delay(3) {
// Find the button's width and height
let buttonWidth = button.frame.width
let buttonHeight = button.frame.height
// Find the width and height of the enclosing view
let viewWidth = button.superview!.bounds.width
let viewHeight = button.superview!.bounds.height
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - buttonWidth
let yheight = viewHeight - buttonHeight
// 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.
button.center.x = xoffset + buttonWidth / 2
button.center.y = yoffset + buttonHeight / 2
self.WaffleButton.hidden = false
self.delay(1) {
self.WaffleButton.hidden = true
}
}
}
}
}
You are using while in a way it is not meant to be used.
This is how you should use while :
var someCondition = true
while someCondition {
// this will loop as fast as possible untill someConditionIsTrue is no longer true
// inside the while statement you will do stuff x number of times
// then when ready you set someCondition to false
someCondition = false // stop
}
This is how you are using while :
let someConditionThatIsAlwaysTrue = true
while someConditionThatIsAlwaysTrue {
// condition is always true, so inifinite loop...
// this creates a function that is executed 3 seconds after the current looping pass of the while loop.
// while does not wait for it to be finished.
// while just keeps going.
// a fraction of a second later it will create another function that will execute 3 seconds later.
// so after 3 seconds an infite amount of functions will execute with a fraction of a second between them.
// except they won't, since the main thread is still busy with your infinite while loop.
delay(3) {
// stuff
}
}
How to do it the right way :
don't ever use while or repeat to "plan" delayed code execution.
Split up the problem in smaller problems:
Issue 1 : Creating a loop
A loop is created by have two functions that trigger each other.
I will call them execute and executeAgain.
So execute triggers executeAgain and executeAgain triggers execute and then it starts all over again -> Loop!
Instead of calling execute and executeAgain directly, you also create a start function. This is not needed but it is a good place to setup conditions for your looping function. start will call execute and start the loop.
To stop the loop you create a stop function that changes some condition.
execute and executeAgain will check for this condition and only keep on looping if the check is successful. stop makes this check fail.
var mustLoop : Bool = false
func startLoop() {
mustLoop = true
execute()
}
func execute() {
if mustLoop {
executeAgain()
}
}
func executeAgain() {
if mustLoop {
execute()
}
}
func stop() {
mustLoop = false
}
Issue 2: Delayed Execution
If you need a delay inside a subclass of NSObject the most obvious choice is NSTimer. Most UI classes (like UIButton and UIViewController) are subclasses of NSObject.
NSTimer can also be set to repeat. This would also create a loop that executes every x seconds. But since you actually have 2 alternating actions it makes more sense to adopt the more verbose looping pattern.
An NSTimer executes a function (passed as Selector("nameOfFunction")) after x amount of time.
var timer : NSTimer?
func planSomething() {
timer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("doSomething"), userInfo: nil, repeats: false)
}
func doSomething() {
// stuff
}
If you need a delay in another class/struct (or you don't like NSTimer) you can use the delay function that matt posted.
It will execute whatever you enter in the closure after x amount of time.
func planSomething() {
delay(3) {
doSomething()
}
}
func doSomething() {
// stuff
}
Combining the two solutions:
By using the loop pattern above you now have distinct functions. Instead of calling them directly to keep the loop going. You insert the delay method of your choice and pass the next function to it.
So NSTimer will have a Selector pointing to execute or executeAgain and with delay you place them in the closure
How to implement it elegantly:
I would subclass UIButton to implement all this. Then you can keep your UIViewController a lot cleaner. Just choose the subclass in IB and connect the IBOutlet as usual.
This subclass has a timer attribute that will replace your delay.
The button action wacked() is also set in the init method.
From your UIViewController you call the start() func of the button. This will start the timer.
The timer will trigger appear() or disappear.
wacked() will stop the timer and make the button hide.
class WackingButton : UIButton {
var timer : NSTimer?
var hiddenTime : NSTimeInterval = 3
var popUpTime : NSTimeInterval = 1
override init(frame: CGRect) {
super.init(frame: frame)
self.addTarget(self, action: "wacked", forControlEvents: UIControlEvents.TouchUpInside)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addTarget(self, action: "wacked", forControlEvents: UIControlEvents.TouchUpInside)
}
func start() {
timer = NSTimer.scheduledTimerWithTimeInterval(hiddenTime, target: self, selector: Selector("appear"), userInfo: nil, repeats: false)
}
func appear() {
self.center = randomPosition()
self.hidden = false
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(popUpTime, target: self, selector: Selector("dissappear"), userInfo: nil, repeats: false)
}
func dissappear() {
self.hidden = true
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(hiddenTime, target: self, selector: Selector("appear"), userInfo: nil, repeats: false)
}
func wacked() {
self.hidden = true
timer?.invalidate()
}
func randomPosition() -> CGPoint {
// Find the width and height of the enclosing view
let viewWidth = self.superview?.bounds.width ?? 0 // not really correct, but only fails when there is no superview and then it doesn't matter anyway. Won't crash...
let viewHeight = self.superview?.bounds.height ?? 0
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - frame.width
let yheight = viewHeight - frame.height
// 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.
let x = xoffset + frame.width / 2
let y = yoffset + frame.height / 2
return CGPoint(x: x, y: y)
}
}
Your UIViewController :
class ViewController: UIViewController {
#IBOutlet weak var button1: WackingButton!
override func viewDidAppear(animated: Bool) {
button1.start()
}
}

Is there a way to programmatically change the value of a UIStepper based on a calculation?

Let's say I have 3 UISteppers.
When I change the value of one, the values of the other steppers change based on a formula.
Stepper one value is 10.
Stepper two value is 5.
Stepper three value is 3.
If I change stepper 10 +/- 1, it might change the value in stepper two +/- one.
Is this possible?
It is absolutely possible.
You can imagine this scenario:
UIStepper *stepperOne = [[UIStepper alloc] initWithFrame:frame];
[stepperOne addTarget:self action:#selector(stepperOneChanged:) forControlEvents:UIControlEventValueChanged];
- (void)stepperOneChanged:(UIStepper*)stepperOne{
//This method would be called on the target of your first stepper on UIControlEventsValueChanged
//Decrease the value by 1
stepperTwo.value --;
//OR
//Increase the value by 1
stepperTwo.value ++;
}
Swift 4.2
override func viewDidLoad() {
super.viewDidLoad()
let stepper = UIStepper(frame: CGRect(x: 100, y: 200, width: 0, height: 0))
stepper.wraps = true
stepper.autorepeat = true
stepper.maximumValue = 20
stepper.addTarget(self, action: #selector(stepperValueChanged(_:)), for: .valueChanged)
view.addSubview(stepper)
}
#objc func stepperValueChanged(_ stepper: UIStepper) {
print("Chnaged Value: \(stepper.value)")
stepper.value += 2
print("Chnaged Value: \(stepper.value)")
}

Resources