I would like to make a slider stop at discrete points that represent integers on a timeline. What's the best way to do this? I don't want any values in between. It would be great if the slider could "snap" to position at each discrete point as well.
The steps that I took here were pretty much the same as stated in jrturton's answer but I found that the slider would sort of lag behind my movements quite noticeably. Here is how I did this:
Put the slider into the view in Interface Builder.
Set the min/max values of the slider. (I used 0 and 5)
In the .h file:
#property (strong, nonatomic) IBOutlet UISlider *mySlider;
- (IBAction)sliderChanged:(id)sender;
In the .m file:
- (IBAction)sliderChanged:(id)sender
{
int sliderValue;
sliderValue = lroundf(mySlider.value);
[mySlider setValue:sliderValue animated:YES];
}
After this in Interface Builder I hooked up the 'Touch Up Inside' event for the slider to File's Owner, rather than 'Value Changed'. Now it allows me to smoothly move the slider and snaps to each whole number when my finger is lifted.
Thanks #jrturton!
UPDATE - Swift:
#IBOutlet var mySlider: UISlider!
#IBAction func sliderMoved(sender: UISlider) {
sender.setValue(Float(lroundf(mySlider.value)), animated: true)
}
Also if there is any confusion on hooking things up in the storyboard I have uploaded a quick example to github: https://github.com/nathandries/StickySlider
To make the slider "stick" at specific points, your viewcontroller should, in the valueChanged method linked to from the slider, determine the appropriate rounded from the slider's value and then use setValue: animated: to move the slider to the appropriate place. So, if your slider goes from 0 to 2, and the user changes it to 0.75, you assume this should be 1 and set the slider value to that.
What I did for this is first set an "output" variable of the current slider value to an integer (its a float by default). Then set the output number as the current value of the slider:
int output = (int)mySlider.value;
mySlider.value = output;
This will set it to move in increments of 1 integers. To make it move in a specific range of numbers, say for example, in 5s, modify your output value with the following formula. Add this between the first two lines above:
int output = (int)mySlider.value;
int newValue = 5 * floor((output/5)+0.5);
mySlider.value = newValue;
Now your slider "jumps" to multiples of 5 as you move it.
I did as Nathan suggested, but I also want to update an associated UILabel displaying the current value in real time, so here is what I did:
- (IBAction) countdownSliderChanged:(id)sender
{
// Action Hooked to 'Value Changed' (continuous)
// Update label (to rounded value)
CGFloat value = [_countdownSlider value];
CGFloat roundValue = roundf(value);
[_countdownLabel setText:[NSString stringWithFormat:#" %2.0f 秒", roundValue]];
}
- (IBAction) countdownSliderFinishedEditing:(id)sender
{
// Action Hooked to 'Touch Up Inside' (when user releases knob)
// Adjust knob (to rounded value)
CGFloat value = [_countdownSlider value];
CGFloat roundValue = roundf(value);
if (value != roundValue) {
// Almost 100% of the time - Adjust:
[_countdownSlider setValue:roundValue];
}
}
The drawback, of course, is that it takes two actions (methods) per slider.
Swift version with ValueChanged and TouchUpInside.
EDIT: Actually you should hook up 3 events in this case:
yourSlider.addTarget(self, action: #selector(presetNumberSliderTouchUp), for: [.touchUpInside, .touchUpOutside, .touchCancel])
I've just pasted my code, but you can easily see how it's done.
private func setSizesSliderValue(pn: Int, slider: UISlider, setSliderValue: Bool)
{
if setSliderValue
{
slider.setValue(Float(pn), animated: true)
}
masterPresetInfoLabel.text = String(
format: TexturingViewController.createAndSendPresetNumberNofiticationTemplate,
self.currentPresetNumber.name.uppercased(),
String(self.currentPresetNumber.currentUserIndexHumanFriendly)
)
}
#objc func presetNumberSliderTouchUp(_ sender: Any)
{
guard let slider = sender as? NormalSlider else{
return
}
setupSliderChangedValuesGeneric(slider: slider, setSliderValue: true)
}
private func setupSliderChangedValuesGeneric(slider: NormalSlider, setSliderValue: Bool)
{
let rounded = roundf((Float(slider.value) / Float(presetNumberStep))) * Float(presetNumberStep)
// Set new preset number value
self.currentPresetNumber.current = Int(rounded)
setSizesSliderValue(pn: Int(rounded), slider: slider, setSliderValue: setSliderValue)
}
#IBAction func presetNumberChanged(_ sender: Any)
{
guard let slider = sender as? NormalSlider else{
return
}
setupSliderChangedValuesGeneric(slider: slider, setSliderValue: false)
}
Related
Using code found in another post on here, I was able to programmatically draw and erase a subview, including location, width, and height, as shown in func addLoadButton and func removeSubview below. I have also figured out how to programmatically draw a picture on the main View Controller, as shown in func trailerLoadImage below. However, after many hours and attempts, I have tried to programmatically add and remove images into that subview without success.
My end goal is to be able to press three different trailer load type buttons to insert three different images (button 1 loads image 1, button 2 loads image 2, etc.) in a subview located in a specific location on the screen, and to be able to remove the images one at a time (may not be in order put on screen) by tapping on the images with a finger. The subview can be permanent or can be created and removed programmatically (as used below).
What code would I use to insert an image or multiple different images into a subview that has already been created, to remove the image(s) in the reverse order added, and to clear all images out of the subview? If this can’t be done, an acceptable alternative would be the ability to remove the image from the main VC by either tapping on it or pressing a button to clear all added images.
//Class declaration
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {
//Boolean to include load type one in calculations
var trailerLoad : Bool = false
var trailerLoadDistanceFromFront = 20
//Boolean to include load type two in calculations
var trailerLoadTwo : Bool = false
var trailerLoadTwoDistanceFromFront = 80
//Boolean to include load type three in calculations
var trailerLoadThree : Bool = false
var trailerLoadThreeDistanceFromFront = 120
var trailerLoadWidth : Int = 0
var trailerLoadX : Int = 0
//Boolean true only when subView on trailer is active
var subViewActive : Bool = false
override func viewDidLoad() {
super.viewDidLoad()
//Picker view data sources and delegates included in here and work fine
}
//Adds subview for loads
#IBAction func addLoadButton(_ sender: Any) {
let trailerLoadView: UIView = UIView(frame: CGRect(x: 252, y: 233, width: 378, height: 100))
trailerLoadView.backgroundColor = .blue
trailerLoadView.alpha = 0.5
trailerLoadView.tag = 100
trailerLoadView.isUserInteractionEnabled = true
self.view.addSubview(trailerLoadView)
subViewActive = true
}
//If subViewActive is true, calls alert to get distance load type one is from front, moves on to insert and position image, changes trailerLoad bool to true
#IBAction func trailerLoadOneButton(_ sender: Any) {
//If subViewActive is true:
//Calls alert to get distance load type one is from front, puts in var trailerLoadDistanceFromFront
//Calls trailerLoadImage() to insert and position load type one image
//Changes bool trailerLoad to true
//If subViewActive is false:
//Calls alert to tell user that they need to click Add Load button (create subview) before adding load types one, two, or three
}
//Add trailer load type one image, scales and positions it relatively accurately in view.
//To be duplicated and modified for load types two and three in the future, with different images (trailerLoadTypeTwoPic and trailerLoadTypeThreePic)
func trailerLoadImage() {
trailerLoadWidth = 378 * 60 / trailerTotalLength
trailerLoadX = 378 * trailerLoadDistanceFromFront / trailerTotalLength
let imageView = UIImageView(frame: CGRect(x: (252 + trailerLoadX), y: (333 - trailerLoadWidth), width: trailerLoadWidth, height: trailerLoadWidth));
let image = UIImage(named: “trailerLoadTypeOnePic”);
imageView.image = image;
self.view.addSubview(imageView)
}
//Calls func removeSubview to remove subview
#IBAction func resetButton(_ sender: Any) {
removeSubview()
}
//Removes subview for loads
#objc func removeSubview(){
subViewActive = false
if let viewWithTag = self.view.viewWithTag(100) {
viewWithTag.removeFromSuperview()
}else{
print("No!")
}
}
}
Thank you very much to anybody that offers assistance or advice.
Don't use tags! Just create variables in global scope for your views
var imageViews = [UIImageView]()
then when you need to add them first append them to your array and then add them to view
imageViews.append(imageView)
view.addSubview(imageView)
Then when you need to remove your all views from their superview, use method removeFromSuperview() for each view in array
imageViews.forEach { $0.removeFromSuperview() }
imageViews.removeAll()
or if you need to remove just one view at specific index
imageViews[index].removeFromSuperview()
imageViews.remove(at: index)
I've attempted this multiple times, 1st by just trying to add the labels under my sliders (see picture below), but labels do not support integers. My newest attempt used 4 integer variables and set the label value to it, the only thing wrong with this method is it only works with one label, if I do this for all 4 I cannot add them together. `
var aValue: Int {
didSet {
mathCriAValue.text = "\(aValue)"
}
}
#IBAction func mathCriAChanged( sender: UISlider) {
aValue = Int(sender.value)
}
Main storyboard
Create a reference to each of your sliders and each of your labels (i.e. make them all IBOutlet properties).
When your slider event is triggered, check which slider is the sender and update the text in the appropriate label:
labelA.text = "\(sliderA.value)"
Sum up the values of all sliders:
let total = sliderA.value + sliderB.value + sliderC.value + sliderD.value
Set the text in the total label (to which you also need a reference):
totalLabel.text = "\(total)"
I've been doing some research about making Apple Watch apps, but I'm having some trouble getting the value of the Digital Crown. I looked at the WKCrownSequencer but not sure what to do with it. Could someone show me how I would go about getting a variable with values 1-10 that change when you turn the Digital Crown. Thanks!
You need to conform your InterfaceController subclass to WKCrownDelegate and implement the crownDidRotate method.
If you want your value to be between 1 and 10, you just need to implement some simple logic for checking what would the value be when you added the rotationalDelta and if it would be out of the range 1-10, map value to either 1 or 10, depending on which direction the new value would exceed. I have assumed you want value to be Int, if not, just remove the conversion of rotationalDelta to Int and value will be Double.
Keep in mind, that a rotationalDelta of 1.0 represents a full rotation of the crown and rotationalDelta changes its sign based on the direction of the rotation.
class MyInterfaceController: WKInterfaceController, WKCrownDelegate {
var value = 1
#IBOutlet var label: WKInterfaceLabel!
override func awake(withContext context: Any?) {
super.awake(withContext: context)
crownSequencer.delegate = self
}
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
let newValue = value + Int(rotationalDelta)
if newValue < 1 {
value = 1
} else if newValue > 10 {
value = 10
} else {
value = newValue
}
label.setText("Current value: \(value)")
}
}
After watchOS 3
After watchOS3 it's possible to take value of Digital Crown. Check this documentation on Apple.
Basically you need to use WKCrownDelegate in your View Controller. An example code will something like this:
- (void)willActivate {
[super willActivate];
self.crownSequencer.delegate = self;
[self.crownSequencer focus];
}
- (void)crownDidRotate:(WKCrownSequencer *)crownSequencer rotationalDelta:(double)rotationalDelta {
self.totalDelta = self.totalDelta + rotationalDelta;
[self updateLabel];
}
- (void)updateLabel {
[self.label setText:[NSString stringWithFormat:#"%f", self.totalDelta]];
}
What is rotationalDelta
A value of 1.0 appears to indicate that the crown has completed a full rotation.
Warning
Please don't use this code directly, it's just an example. Check documentation from Apple's site first.
I followed this awesome Rey Wenderlich tutorial to make an Bezier arc and increment/decrement values. But how to animate the arch instead of just step-up and step-down?
http://www.raywenderlich.com/90690/modern-core-graphics-with-swift-part-1
I tried putting animation block in custom property declaration, which I dont think is the right place to do it and xcode doesn't let me do it anyway.
#IBInspectable var counter: Int = 5 {
didSet {
if counter <= NoOfGlasses {
//the view needs to be refreshed
UIView.animateWithDuration(0.2, animations: {
setNeedsDisplay()
}, completion:nil
)
}
}
}
Also tried to put the increment in animation block in View controller, didn't work.
#IBAction func btnPushButton(sender: AnyObject) {
UIView.animateWithDuration(0.2, animations: {
self.arcView.counter = self.arcView.counter + 10
self.counterLabel.text = String(self.arcView.counter)
}, completion:nil
)
}
Are you describing this sort of thing?
That's a simpler example - it's just a drawn triangle - but it's the same idea, if I'm understanding you correctly: we are animating the difference between one drawing and another.
Basically you have two choices. The easy way is to use CAShapeLayer, which animates for you automatically when you change its path. The other choice is to do what I'm doing here, which is to create a custom animatable property - in this case, a property representing the x-position of the bottom point of the triangle.
A UISlider in my app sometimes shows a second thumb after it is dragged. This is completely unintentional and unwanted. I've done some searching and can't find any other references to this phenomenon. Can anyone figure out why this is happening? Is this a bug?
Two thumb slider:
This code is executed when a "show options" button is pressed:
readAndApplySettings() //This sets GameSpeed from a file
var SpeedSlider = UISlider()
SpeedSlider.minimumValue = 1
SpeedSlider.maximumValue = 20
SpeedSlider.setValue(Float(21 - GameState.GameSpeed), animated: true)
SpeedSlider.continuous = true
SpeedSlider.addTarget(self, action: "SpeedSliderValueDidChange:", forControlEvents: .ValueChanged)
// Code setting frame and location omitted
OptionsLabelButton.addSubview(SpeedSlider)
This is the ValueDidChange code:
func SpeedSliderValueDidChange(sender:UISlider) {
GameState.GameSpeed = Int(21 - sender.value)
TimerInterval = Double(GameState.GameSpeed) * 0.0167
tempSpeedLabel.text = "Speed: \(21 - GameState.GameSpeed)"
}
Looks like you have created multiple UISliders, double check you are not calling your function twice. As well, if you are using storyboard to create the UISliders. Make sure you do not have two sliders on top of each other.