Why does the UISlider view ignore the alpha view when set to 0.5?
Code:
for (int i = 0; i < 3; i++) {
UISlider *slider = [[[UISlider alloc]
initWithFrame:CGRectMake(0, i * 30, 200, 30)]
autorelease];
slider.alpha = 0.4 + (CGFloat)i / 10.0f;
[window addSubview:slider];
}
Result:
The sliders have alpha values 0.4, 0.5 and 0.6. And as you can see the middle one with 0.5 is completely opaque. It seams to only occur with alpha 0.5. Have tested other UI controllers and they work as expected with alpha is set to 0.5.
Reproduced with iOS 4.2 on real device and with iOS 3.2 and 4.2 in simulator.
BTW if someone curious how and why I hit this problem it's the sliding direction pad configuration for a puzzle game called Slippy.
As you said that other UI controllers work with 0.5 alpha, there should be no difference with UISlider, since they inherit alpha property from UIView class and there is reference to the opaque property ("You should always set the value of this property to NO if the view is fully or partially transparent"). Maybe you can try to follow the advice.
If there's really a bug with 0.5 value, you can simply change your starting transparency from 0.4 to 0.41/0.39 w/o any visible difference:
slider.alpha = 0.41f + (CGFloat)i / 10.0f;
Finally, you can output the resulting alpha values to some labels to check if they are the expected ones or output the (CGFloat)i value to see if something wrong with type casting.
Related
I'm attempting to periodically change the background color of my SKScene node every 10 seconds. I'm using a SKAction to fade the SKScene background color to a new, randomly generated color, with the fade action lasting for 4 seconds. Right now, my attempted implementation uses a 'helper' SKSpriteNode to:
obtain a new background to use
set its color
apply an alpha fade action to it
and set it as the first node in my SKScene's node tree
In addition, I have some time keeping code to make sure that the background color on my SKScene is only changed once every 10 seconds. This means that if I assume SpriteKit aiming for 60FPS, the background color should be changed once every 600 seconds. Since each background color fade action should last 4 seconds, each fade should take 240 seconds (again, assuming 60FPS to keep it simple). Basically, there would be a period of 360 seconds where nothing is happening with the background color.
Anyway, I can't seem to get my code to currently work, as the color doesn't change at all. The default background color I set for my SKScene (within my init method) is RGB white. This is the only color that displays each frame. In addition, 'timeSinceUpdateCalled' and 'timeSinceTheLastBackgroundColorChange' are both initially set to 0.0
Everything is driven from the SKScene's -update:(CFTimeInterval) method. Code below:
-(void)update:(CFTimeInterval)currentTime {/* Called before each frame is rendered. Basically, any actions taken on the title scene will go in here*/
CFTimeInterval timeSinceUpdate = currentTime - self.timeSinceUpdateCalled;
self.timeSinceUpdateCalled = currentTime;
[self changeBackgroundTitleColorEverySoOften:timeSinceUpdate];
}
-(void)changeBackgroundTitleColorEverySoOften:(CFTimeInterval)timeSinceUpdate{
self.timeSinceTheLastBackGroundColorChange += timeSinceUpdate;
if(self.timeSinceTheLastBackGroundColorChange > 10.0){
self.timeSinceTheLastBackGroundColorChange = 0;
[self renderNewTitleBackGroundColor];
}
}
-(void)renderNewTitleBackGroundColor{
_batball_titlehelpersprite = [[SKSpriteNode alloc] initWithColor:self.colorGenerator size:self.view.bounds.size];
_batball_titlehelpersprite.alpha = 0.0;
SKAction* titleactioncolorfade = [SKAction fadeInWithDuration:4];
titleactioncolorfade.speed = 1.0;
[_batball_titlehelpersprite runAction:titleactioncolorfade];
[self insertChild:_batball_titlehelpersprite atIndex:1];
}
-(UIColor* )colorGenerator{//This method generates a random UIColor
UIColor* generatedColor = nil;//The generated random color
generatedColor = [UIColor colorWithRed:[BatBallUtils randomFloat2:255] green:[BatBallUtils randomFloat2:255] blue:[BatBallUtils randomFloat2:255] alpha:0.0];//Generate a random color
return generatedColor;
}
I stepped through this code, and it seems that everything should work. My UIColor generator code works as expected as well. This is the code I use to generate a random float number between 2 and 255 for RGB:
+(float)randomFloat2:(int)maxNum{//Generate a random float number between two floating point min and max numbers
float num = (arc4random() % maxNum) / (float)maxNum;
return(num);
}
Does anyone have any ideas what I'm doing wrong?
UIColor values are in the range 0.0 to 1.0, see https://developer.apple.com/library/ios/documentation/uikit/reference/UIColor_Class/Reference/Reference.html#//apple_ref/occ/clm/UIColor/colorWithRed:green:blue:alpha:
Also be sure to use 1.0 for alpha otherwise the color might not render at all because it's fully transparent.
I want to give the following aspect to an UISegmentedControl:
Note the gray background view, and the white background of the segmented control non selected item.
But, if I give a white background to my UISegmentedControl, I get the following:
Note the white square corners around the UISegmentedControl. What should I do to avoid that square corners?
Thank you in advance,
EDIT: If I change the corner radius of the UISegmentedControl's layer, as suggested by onegray, the result is better, but not perfect (note the white line at the right):
Setting the _segmentedControl.layer.cornerRadius = 5; might help.
Update: More complex clip rect to get rid of 1px right space:
CAShapeLayer* mask = [[CAShapeLayer alloc] init];
mask.frame = CGRectMake(0, 0, _segmentedControl.bounds.size.width-1, _segmentedControl.bounds.size.height);
mask.path = [[UIBezierPath bezierPathWithRoundedRect:mask.frame cornerRadius:4] CGPath];
_segmentedControl.layer.mask = mask;
Update: Matthias Bauch provided a good explanation why this whitespace appears on the right side of the UISegmentedControl. So the simplest way to remove it is making segments of fixed size and adjusting them for proper width.
If that should work for all UISegmentedControls it's a bit of a hassle.
The problem is in iOS7 the 1 pt. border between two segments does not count to the size of the segment. E.g. if the frame of your UISegmentedControl is 320 pt. wide you have to remove 1 pt. and than divide by 2.
And (320-1)/2 is 159.5. iOS floors this value down to 159 pt. And you end up with a 1 pt. border and two 159 pt. segments. Which is 319, and not 320. Hence the 1pt. line at the right of your segmentedControl.
There is a way to calculate the "actual" (the size of the rendering on screen) size of the segmentedControl. With that width you can then add a UIView with rounded corners below the UISegmentedControl.
This code should work for all configurations, even if you have manually sized segments in your segmentedControl:
- (UIView *)addBackgroundViewBelowSegmentedControl:(UISegmentedControl *)segmentedControl {
CGFloat autosizedWidth = CGRectGetWidth(segmentedControl.bounds);
autosizedWidth -= (segmentedControl.numberOfSegments - 1); // ignore the 1pt. borders between segments
NSInteger numberOfAutosizedSegmentes = 0;
NSMutableArray *segmentWidths = [NSMutableArray arrayWithCapacity:segmentedControl.numberOfSegments];
for (NSInteger i = 0; i < segmentedControl.numberOfSegments; i++) {
CGFloat width = [segmentedControl widthForSegmentAtIndex:i];
if (width == 0.0f) {
// auto sized
numberOfAutosizedSegmentes++;
[segmentWidths addObject:[NSNull null]];
}
else {
// manually sized
autosizedWidth -= width;
[segmentWidths addObject:#(width)];
}
}
CGFloat autoWidth = floorf(autosizedWidth/(float)numberOfAutosizedSegmentes);
CGFloat realWidth = (segmentedControl.numberOfSegments-1); // add all the 1pt. borders between the segments
for (NSInteger i = 0; i < [segmentWidths count]; i++) {
id width = segmentWidths[i];
if (width == [NSNull null]) {
realWidth += autoWidth;
}
else {
realWidth += [width floatValue];
}
}
CGRect whiteViewFrame = segmentedControl.frame;
whiteViewFrame.size.width = realWidth;
UIView *whiteView = [[UIView alloc] initWithFrame:whiteViewFrame];
whiteView.backgroundColor = [UIColor whiteColor];
whiteView.layer.cornerRadius = 5.0f;
[self.view insertSubview:whiteView belowSubview:segmentedControl];
return whiteView;
}
Please take care of frame changes yourself.
See this screenshot to see the difference between the two controls. All frames are 280 pt. wide.
Because of the formula UISegmentedControl uses the first controls actual size is 278 pt. And the real size of the second one is 279 pt.
The problem is that this somehow relies on the implementation of UISegmentedControl. Apple could for example change the implementation so segmentWidth that end in .5 points will be displayed. They could easily do this on retina displays.
If you use this code you should check your app on new iOS versions as early as possible. We are relying on implementation details, and those could change every day. Fortunately nothing bad happens if they change the implementation. It will just not look good.
I know this is kind of a hack but you could just use a rounded UIView with white background placed just underneath - and aligned with - the segmented control, except for the width which should be equal to the original control's width minus 1.
Result:
Just to clarify Mattias Bauch's excellent answer. You need to set the returned view as a subview to the view (which we call yourMainView) where you have your segmented control:
UIView *segmControlBackground = [self addBackgroundViewBelowSegmentedControl:yourSegmentedControl];
[yourMainView addSubview:segmControlBackground];
And you need to, of course, declare the new method in your header (.h) file:
- (UIView *)addBackgroundViewBelowSegmentedControl:(UISegmentedControl *)segmentedControl;
I have a UIView object that rotates using CALayer's transform:
// Create uiview object.
UIImageView *block = [[UIImageView alloc] initWithFrame....]
// Apply rotation.
CATransform3D basicTrans = CATransform3DIdentity;
basicTrans.m34 = 1.0/-distance;
blockImage.layer.transform = CATransform3DRotate(basicTrans, rangle, 1.0f, 0.0f, 0.0f);
After rotating the edges of the object are not antialiasing. I need to antialias them.
Help me, please. How can it be done?
One way to do this is by placing the image inside another view that's 5 pixels bigger. The bigger view should have a transparent rasterized border that will smooth the edges of the UIImageView:
view.layer.borderWidth = 3;
view.layer.borderColor = [UIColor clearColor].CGColor;
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [[UIScreen mainScreen] scale];
Then, place your UIImageView inside this parent view and center it (With 2.5 pixels around each edge).
Finally, rotate the parent view instead of the image view.
It works very well - you can also encapsulate the whole thing in class that creates the hierarchy.
Simply add this key-value pair to your Info.plist: UIViewEdgeAntialiasing set to YES.
check allowsEdgeAntialiasing property of CALayer.
block.layer.allowsEdgeAntialiasing = YES; // iOS7 and above.
I had a similar issue when rotating around the z-axis. Setting shouldRasterize = YES prevented the jagged edges however it came at a performance cost. In my case I was re-using the views (and its layers) and keeping the shouldRasterize = YES was slowing things down.
The solution was, to turn off rasterization right after I didn't need it anymore. However since animation runs on another thread, there was no way of knowing when the animation was complete...until I found out about an extremely useful CATransaction method. This is an actual code that I used and it should illustrate its use:
// Create a key frame animation
CAKeyframeAnimation *wiggle = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
NSInteger frequency = 5; // Higher value for faster vibration
NSInteger amplitude = 25; // Higher value for lower amplitude
// Create the values it will pass through
NSMutableArray *valuesArray = [[NSMutableArray alloc] init];
NSInteger direction = 1;
[valuesArray addObject:#0.0];
for (NSInteger i = frequency; i > 0; i--, direction *= -1) {
[valuesArray addObject:#((direction * M_PI_4 * (CGFloat)i / (CGFloat)amplitude))];
}
[valuesArray addObject:#0.0];
[wiggle setValues:valuesArray];
// Set the duration
[wiggle setAdditive:YES];
[wiggle setValueFunction:[CAValueFunction functionWithName:kCAValueFunctionRotateZ]];
[wiggle setDuration:0.6];
// Turn on rasterization to prevent jagged edges (anti-aliasing issues)
viewToRotate.layer.shouldRasterize = YES;
// ************ Important step **************
// Very usefull method. Block returns after ALL animations have completed.
[CATransaction setCompletionBlock:^{
viewToRotate.layer.shouldRasterize = NO;
}];
// Animate the layer
[viewToRotate.layer addAnimation:wiggle forKey:#"wiggleAnimation"];
worked like a charm for me.
I have not tried using this with implicit animations (i.e. animations that happen due to value change in animatable property for a non-view associated layer), however I would expect it to work as long as the CATransaction method is called before the property change, just as a guarantee the block is given to CATransaction before an animation starts.
I'm stuck at the moment controlling UISlider in XCode. I've made a horizontal slider in Interface Builder and manage to code the controls in xcode. Right now I'm just not sure how to code the logic. If the user slides the nib to the left, I'd it like it to rotate my image counter-clockwise and if the user slides it to the right, rotate the image clockwise.
Minimum value of slider I've set is 0 and max is 100. Initial value is 50.
For transformation I'm using CGAffineTransformMakeRotation(myAngle). I've set myAngle to float. As for the condition, here's a snippet of code:
mySlider.value = myAngle; // This is for CGAffine
if(myAngle < 50) {
myAngle -= 0.1;
}
if(myAngle > 50) {
myAngle += 0.1;
}
When I slide the nib, it only rotates anti-clockwise. What's the best logic can I use? Hope someone can help. Thanks.
-Hakimo
Note that the Doc says about the angle as,
The angle, in radians, by which this matrix rotates the coordinate system axes. In iOS, a positive value specifies counterclockwise rotation and a negative value specifies clockwise rotation. In Mac OS X, a positive value specifies clockwise rotation and a negative value specifies counterclockwise rotation.
I think something like the following should work. I am not sure though. (Considering that the minimumValue is 0).
float middleValue = slider.maximumValue / 2;
float value = middleValue - slider.value;
float degrees = (middleValue / 180) * value;
float radians = degrees * (3.14/180);
If you want a fixed position on the slider to correspond to a fixed angle of rotation, then you can do something like this:
float degreesToRadians = M_PI / 180.0;
CGFloat angle = (mySlider.value - 50) * degreesToRadians;
myView.transform = CGAffineTransformMakeRotation(angle);
On the other hand, if want more complex behavior, such as that the slider snaps to the middle except when being dragged, and how far it's dragged determines the speed of rotation of your view, then I would suggest using a timer. Set up an NSTimer to call a given selector, and in that selector do something like this:
- (void)timerFired:(NSTimer *)aTimer {
CGFloat angleDelta = (mySlider.value - 50) / 500;
// I'll assume myAngle is an instance variable.
myAngle += angleDelta;
myView.transform = CGAffineTransformMakeRotation(myAngle);
}
And somewhere else you need to write a method to set mySlider.value = 50 when the user stops dragging it, which you can do by adding a target/action for UIControlEventTouchUpInside and ...Outside on the slider.
If you want, you can make the rotation appear very smooth by using UIView animations each time you set the transform, using an animation duration equal to the NSTimer interval. If you do that, then I would actually suggest using the animation callback in place of the timer method, to avoid any possible crossover between the two events.
This is quite the iPhone quandry. I am working on a library, but have narrowed down my problem to very simple code. What this code does is create a 50x50 view, applies a rotation transform of a few degrees, then shifts the frame down a few times. The result is the 50x50 view is now much larger looking.
Here's the code:
// a simple 50x50 view
UIView *redThing = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)];
redThing.backgroundColor = [UIColor redColor];
[self.view addSubview:redThing];
// rotate a small amount (as long as it's not 90 or 180, etc.)
redThing.transform = CGAffineTransformRotate(redThing.transform, 0.1234);
// move the view down 2 pixels
CGRect newFrame = CGRectMake(redThing.frame.origin.x, redThing.frame.origin.y + 2, redThing.frame.size.width, redThing.frame.size.height);
redThing.frame = newFrame;
// move the view down another 2 pixels
newFrame = CGRectMake(redThing.frame.origin.x, redThing.frame.origin.y + 2, redThing.frame.size.width, redThing.frame.size.height);
redThing.frame = newFrame;
// move the view down another 2 pixels
newFrame = CGRectMake(redThing.frame.origin.x, redThing.frame.origin.y + 2, redThing.frame.size.width, redThing.frame.size.height);
redThing.frame = newFrame;
// move the view down another 2 pixels
newFrame = CGRectMake(redThing.frame.origin.x, redThing.frame.origin.y + 2, redThing.frame.size.width, redThing.frame.size.height);
redThing.frame = newFrame;
So, what the heck is going on? Now, if I move the view by applying a translation transform, it works just fine. But that's not what I want to do and this should work anyway.
Any ideas?
From the UIView documentation:
If the transform property is also set, use the bounds and center properties instead; otherwise, animating changes to the frame property does not correctly reflect the actual location of the view.
Warning: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
In other words, I would be wary of the frame property when a transform is set.