I have a UISlider with value 0, minimum value 0 and maximum value 360 (set from the utilities area), and I want the value to increment by 15 (have an interval size of 15) with each swipe through VoiceOver, and have it snap with each swipe. e.g. swiping right once -> 15, twice -> 30 etc. I've been listening to what the current value on the slider is using VoiceOver.
I've already tried something similar to:
float interval = 5.0f;//set this
[slider setValue:interval*floorf((slider.value/interval)+0.5f) animated:NO];
source: https://stackoverflow.com/a/2519573/11693942
what I did based off of this code was:
angle = Double(15 * floorf((sender.value/15) + 0.5))
sender.setValue(Float(angle), animated: true)
but this makes my slider increment by 30.
Next, I tried:
angle = Double(15 * floorf((sender.value/30) + 0.5))
sender.setValue(Float(angle), animated: true)
which does increment my slider by 15, but then the slider gets stuck at 30. I can't increase the value, but I can decrease it. At this point VoiceOver keeps saying the value is 30, too.
I didn't get any crashes or error messages. The rest of my app still works, too.
Perhaps I misunderstood your question, but how about slider.value = slider.value + 15.0? Or slider.setValue(slider.value + 15.0, animated: true) if you want it animated.
That would be the easiest way to increase the value of a slider by 15.
Resolved with the help of a teammate :) ! Closing.
#IBAction func angleSliderChanged(_ sender: UISlider) {
let roundingNumber: Float = (interval/2.0)
angle = Double(sender.value)
sender.accessibilityValue = "\(Int(angle)) degrees"
sender.setValue(interval*floorf(((sender.value+roundingNumber)/interval)), animated:false)
}
Related
So I'll give as much information about this project as I can right up front. Here is an image of a section of the storyboard that is relevant to the issue:
And here is the flow of the code:
1) A user plays the game. This scrambles up the emoji that are displayed and will eventually hide all of the emoji on the right side.
2) When someone wins the game, it calls
performSegue(withIdentifier: "ShowWinScreenSegue", sender: self)
Which will perform the segue the red arrow is pointing to. This segue is a modal segue, over current content, cross dissolve.
3) Stuff goes on here, and then I try to get back to the game screen so the user can play another game. Here is my current code for that
// self.delegate is the GameController that called the segue
// it's set somewhere else in the code so I can call these reset functions
GameController.gs = GameState()
guard let d = self.delegate else {
return
}
d.resetGameToMatchState()
dismiss(animated: true, completion: {
print("Modal dismiss completed")
GameController.gs = GameState()
self.delegate?.resetGameToMatchState()
})
So here's where the issue is. You can see that I have to call delegate?.resetGameToMatchState() twice for anything to actually happen. If I remove the top one, nothing happens when I call the second one and vice-versa. What makes this so annoying is that the user will see a weird jump where all the ui goes from the old state to the new state because it's updating so late and spastically.
What I've tried so far
So this whole issue has made me really confused on how the UI system works.
My first thought was that maybe the function is trying to update the UI in a thread that's executing too early for the UI thread. So I put the whole body of resetGameToMatchState in a DispatchQueue.main.async call. This didn't do anything.
Then I thought that it was working before because when the WinScreenSegue was being dismissed before (when it was a "show" segue) it was calling GameController's ViewDidAppear. I tried manually calling this function in the dismiss callback, but that didn't work either and feels really hacky.
And now I'm stuck :( Any help would be totally appreciated. Even if it's just a little info that can clear up how the UI system works.
Here is my resetGameToMatchState():
//reset all emoji labels
func resetGameToMatchState() {
DispatchQueue.main.async {
let tier = GameController.gs.tier
var i = 0
for emoji in self.currentEmojiLabels! {
emoji.frame = self.currentEmojiLabelInitFrames[i]
emoji.isHidden = false
emoji.transform = CGAffineTransform(scaleX: 1, y: 1);
i+=1
}
i=0
for emoji in self.goalEmojiLabels! {
emoji.frame = self.goalEmojiLabelInitFrames[i]
emoji.isHidden = false
emoji.transform = CGAffineTransform(scaleX: 1, y: 1);
i+=1
}
//match state
for i in 1...4 {
if GameController.gs.currentEmojis[i] == GameController.gs.goalEmojis[i] {
self.currentEmojiLabels?.findByTag(tag: i)?.isHidden = true
}
}
//reset highlight
let f = self.highlightBarInitFrame
let currentLabel = self.goalEmojiLabels?.findByTag(tag: tier)
let newSize = CGRect(x: f.origin.x, y: (currentLabel?.frame.origin.y)!, width: f.width, height: (currentLabel?.frame.height)! )
self.highlightBarImageView.frame = newSize
//update taps
self.updateTapUI()
//update goal and current emojis to show what the current goal/current selected emoji is
self.updateGoalEmojiLabels()
self.updateCurrentEmojiLabels()
}
}
UPDATE
So I just found this out. The only thing that isn't working when I try to reset the UI is resetting the right side emoji to their original positions. What I do is at the start of the app (in viewDidLoad) I run this:
for emoji in currentEmojiLabels! {
currentEmojiLabelInitFrames.append(emoji.frame)
}
This saves their original positions to be used later. I do this because I animate them to the side of the screen before hiding them.
Now when I want to reset their positions, I do this:
var i = 0
for emoji in self.currentEmojiLabels! {
emoji.frame = self.currentEmojiLabelInitFrames[i]
emoji.isHidden = false
emoji.transform = CGAffineTransform(scaleX: 1, y: 1);
i+=1
}
this should set them all to their original frame and scale, but it doesn't set the position correctly. It DOES reset the scale though. What's weird is that I can see a tiny bit of one of the emoji off to the left of the screen and when they animate, they animate from far off on the left. I'm trying to think of why the frames are so off...
UPDATE 2
So I tried changing the frame reset code to this:
emoji.frame = CGRect(x: 25, y: 25, width: 25, height: 25)
Which I thought should reset them correctly to the top left, but it STILL shoves them off to the left. This should prove that the currentEmojiLabelInitFrames are not the issue and that it has something to do with when I'm setting them. Maybe the constraints are getting reset or messed up?
Your first screen, GameController, should receive a viewWillAppear callback from UIKit when the modal WinScreenController is being dismissed.
So its resetGameToMatchState function could simply set a property to true, then your existing resetGameToMatchState could move into viewWillAppear, checking first if the property is being set.
var resetNeeded: Bool = false
func resetGameToMatchState() {
resetNeeded = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// reset code here
}
TLDR; Reset an element's scale BEFORE resetting it's frame or else it will scale/position incorrectly
Finally figured this out. Here's a bit more background. When an emoji is animated off the screen, this is called:
UIView.animate(withDuration: 1.5, animations: {
let newFrame = self.profileButton.frame
prevLabel?.frame = newFrame
prevLabel?.transform = CGAffineTransform(scaleX: 0.1, y: 0.1);
}) { (finished) in
prevLabel?.isHidden = true
}
So this sets the frame to the top left of the screen and then scales it down. What I didn't realize is that when I want to reset the element, I NEED to set the scale back to normal before setting the frame. Here is the new reset code:
for emoji in self.currentEmojiLabels! {
emoji.transform = CGAffineTransform(scaleX: 1, y: 1) //this needs to be first
emoji.frame = self.currentEmojiLabelInitFrames[i] //this needs to be after the scale
emoji.isHidden = false
i+=1
}
I've tried to make a slider to show a percentage out of 100, but the label doesn't show that value correctly.
A UISlider has a default min and max of 0 and 1. So the slider is sending all kinds of fractional values as you move the slider such as 0.153343 and .53453545, etc. But you convert that number to an Int. That leaves you with only 0 and 1.
Either multiply the sender.value * 100 or change the slider's max value to 100.
Just set the maximumValue of the slider:
slider.maximumValue = 100
This will have the slider go between 0 and 100.
However, if you would not like to do this, try something like this:
#IBAction func valueChanged(sender: UISlider) {
let rounded = round(100 * sender.value) / 100
let final = rounded * 100
sliderLabel.text = "\(final)"
}
Made for Objective C. If anybody need.
float founded = roundf(100*self.volumeViewSlider.value)/100;
float final = founded*100;
I want to do something like this. I have a UISlider its minimum value shuld be 0 and the maximum value should be 1000.And I want to divide this range into 10 parts. Like this.
0-----100----200----300----400
When user drag the slider from 0 it should directly stop at 100 position without stopping inbetween 0 and 100 when he drag from 100 it should directly jump into 200 should not stop inbetween 100 and 200 likewise.How can I do this? please help me.
Thanks
You have to do this:
slider.maximumValue = numberOfSteps;
slider.minimumValue = 0;
slider.continuous = NO;
For setting
NSUInteger index = (NSUInteger)(slider_value + 0.5);
[slider setValue:index animated:NO];
Well, just assign your slider range 0...10 and multiply changed value by 100
I had the same need just like you the day before. With default UISlider, you can only increment either +1 or -1. Hence, if you are looking to have a increment in other values, you have to do it programmatically.
In your case, try this:
- (IBAction)[yourUISlider]:(id)sender {
[[yourUISliderProperty] setValue:((int)(( [yourUISlider].value + 50) / 100) * 100) animated:NO];
NSLog(#"%d", [yourUISlider].value );
}
It worked for me, try it out :)
And for other increment values, check out the formula here:
((int)(( [yourSlider] + [yourIncrement/2] ) / [yourIncrement]) *[yourIncrement])
Then NSLog or output the value.
In UISlider Control if I set "MinimumValue = 0" & "MaximimValue=2000" then I am not able to set the exact value. On release of slider thumb image it changes the value to + or - 5 to10. My slider width = 225px.
For example:- if I try to set the slider value to 100 then on release of slider thumb it shows me result like 105 or 95.
Code
IBOutlet UISlider * slider;
//"Slider" Object attached using XIB(.NIB)
slider.minimumValue = 0;
slider.maximumValue = 100;
//Attached Below Method Using XIB(.NIB) with "value Changed" event.
-(IBAction)slider_Change:(id)sender;
{
//Assigning value to UITextField
textFiled.text = [NSString stringWithFormat:#"%d",(((int) (slider.value)) * 20)];
}
Because you have set your slider range to be so large each pixel on the screen is equivalent to 8.888.
2000 / 255 = 8.8888888889
To get graduations of 1 you'll need to move the slider 1/8 of a pixel (this is impossible).
Alternatives are:
Make the slider longer
Reduce the slider range
Use a different control UIStepper
In addition to the answer by #rjstelling, it seems as if you are trying to convert the value of the slider to become an integer value. I believe that the default of the slider is a double value, this would cause the value to become rounded as it is increasing, since there will not be decimal values. Unless it is a requirement, see if you can use double.
textFiled.text = [NSString stringWithFormat:#"%f",(slider.value * 20.0)];
or if for some reason you'd want to restrict the level of decimal places you could go, you could do:
textField.text = [NSString stringWithFormat:#"%.1f", slider.value * 20.0];
This will give you the result of a decimal like 1.5 as your answer.
You could do anything with the decimal or even do something like %1.2f, %4.3f, or even %10.4f,
those will produce results like: 0.34, 0012.446, and something extreme like 000000345.1111
Hope this helps
Is there a good way to reverse UISlider values? The default is min on the left and max on the right. I'd like it to work the opposite way.
Rotating it 180 degrees seems a bit silly. Any ideas?
Thanks!
Just subtract the value you get from the slider from the maximum value, that will reverse the values.
I then needed the exact same thing as you...so rotated it another 90 degrees placing it in an upside down position. Slider is symetrical, so it looks exactly the same in both positions. But the min and max values are now the opposite.
Command is...
mySlider.transform = CGAffineTransformRotate(mySlider.transform, 180.0/180*M_PI);
you can use the following formula:
let sliderValue = yourSlider.minimumValue + yourSlider.maximumValue - yourSlider.value
Will reverse your values.
Subclass NSSlider/UISlider. Override these two methods thus -
//Assumes minValue not necessarily 0.0
-(double)doubleValue
{
double minVal = [self minValue];
double maxVal = [self maxValue];
double curValue = [super doubleValue];
double reverseVal = maxVal - curValue + minVal;
return reverseVal;
}
-(void)setDoubleValue:(double)aDouble
{
double minVal = [self minValue];
double maxVal = [self maxValue];
double reverseVal = maxVal - aDouble + minVal;
[super setDoubleValue:reverseVal];
}
This will invert the values allowing right/top to appear as minimum and left/bottom to appear as maximum
The accepted answer is incorrect because it assumes that the slider's Min value is 0.
In my case, my Min is 1.1 and Max is 8.0
You need to subtract the slider's value from Min+Max value to inverse the value.
Try this beautiful line -
yourSlider.semanticContentAttribute = .forceRightToLeft
This changes the min-max track colors + invert the values + you are not losing your frame like in the transform answers.
Just subtract slider current value from max value.
label.text = [NSString stringWithFormat:#" %.f%% ", 100 - self.slider.value];
You can simply flip the slider horizontally using transform (Swift 5.0):
slider.transform = CGAffineTransform(scaleX: -1, y: 1);
How about this:
slider.maximumValue = -minimumValue;
slider.minimumValue = -maximumValue;
-(void) sliderChanged:(UISlider *) slider {
float value = -slider.value;
// do something with value
}
Then just use -slider.value.