I've created a custom button class to be used in my xibs that is basically just a button with a shadow with a label over it. However, the text in the label appears jagged (as though it is not being anti-aliased). Here's my code for the relevant part of the class (it's a very small class that inherits from UIButton).
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self internalInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self internalInit];
}
return self;
}
- (void)internalInit {
self.backgroundColor = [UIColor colorWithRed:22/255.0 green:72/255.0 blue:143/255.0 alpha:1.0];
CGRect frame = self.frame;
frame.origin = CGPointMake(floorf(frame.origin.x), floorf(frame.origin.y));
//self.frame = CGRectIntegral(frame);
frame = self.titleLabel.frame;
frame.origin = CGPointMake(floorf(frame.origin.x), floorf(frame.origin.y));
//self.titleLabel.frame = CGRectIntegral(frame);
// Shadow
self.layer.shadowOffset = CGSizeMake(0, 1.5);
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOpacity = 0.3;
self.layer.shouldRasterize = YES;
self.layer.shadowPath = [[UIBezierPath bezierPathWithRect:self.bounds] CGPath];
// Corner
self.layer.cornerRadius = 5;
}
I've tried troubleshooting the issue and I've found that this can occur when the origin for the label or the button is set at a non-integer value. However, I've checked the absolute value for both the button and the pixel and they are both set to integer values. I haven't been able to figure out what else could be going wrong and I cannot find any others who have had the same issue.
Generally when jaggies happen it's because the same view is being drawn multiple times over itself. Did you confirm this view is only being drawn once?
Related
I am having a big mental block figuring out something I think easy. Please see this very short video: http://screencast.com/t/eaW7rbECv4Ne.
I would like to draw a rectangle in my layoutSubviews method around the whole area that covers the subviews. I currently draw yellow rect around each term as you can see in the video.
I have a StatementView. In each key tap I am creating new objectView and adding it to my StatementView like below. I add the objectViews to a containerView and try to get the origin and size of the containerView in order to draw the stroke around it. However, it always gives me 0.
How should I update containerViews bounds values after I add the subviews to it?
// Created by ilteris on 1/31/15.
// Copyright (c) 2015 ilteris. All rights reserved.
#implementation QWZStatementView
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.userInteractionEnabled = NO;
self.textField = [[MyUITextField alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
self.textField.inputView = [LNNumberpad defaultLNNumberpad];
self.textField.delegate = self;
self.isNumerator = NO;
self.isDenominator = NO;
[self addSubview:self.textField];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(statementOneTextFieldChanged:) name:UITextFieldTextDidChangeNotification object:nil];
self.containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
[self addSubview:self.containerView];
self.autoresizesSubviews = YES;
self.objectsArray = [NSMutableArray arrayWithCapacity:1];
self.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
}
return self;
}
-(void)statementOneTextFieldChanged:(NSNotification *)notification {
NSLog(#"statementOneTextFieldChanged");
QWZObjectView *objectView = [[QWZObjectView alloc] initWithFrame:self.bounds];
[self.containerView addSubview:objectView];
QWZTerm* term = [QWZQuestionDetailViewController createAndReturnCoreDataTermForJSONDict:objectData];
[objectView createTerm:term];
[self.objectsArray addObject:objectView];
[self setNeedsLayout];
}
- (void)layoutSubviews {
NSLog(#"layoutSubviews StatementView");
[super layoutSubviews];
CGSize totalSize = CGSizeMake(0, 0);
for (QWZObjectView* objectView in self.objectsArray) {
CGSize textSize = objectView.bounds.size;
objectView.frame = CGRectMake(totalSize.width , totalSize.height, textSize.width , textSize.height);
totalSize.width = textSize.width + totalSize.width;
}
CGRect bounds = self.containerView.bounds;
/*
NSLog(#"self.containerView.bounds is %#", NSStringFromCGRect(self.containerView.bounds)); //always 0.
bounds.origin = CGPointMake(totalSize.width/2, self.containerView.bounds.origin.y); //adding this code didn't help at all.
CGFloat borderWidth = 2.0f;
self.bounds = CGRectInset(self.bounds, -borderWidth, -borderWidth);
self.layer.borderColor = [UIColor redColor].CGColor;
self.layer.borderWidth = borderWidth;
*/
bounds.origin = CGPointMake(totalSize.width/2, self.containerView.bounds.origin.y);
self.containerView.bounds = bounds;
}
#end
This is a follow-up question to How to synchronize CALayer and UIView animations up and down a complex hierarchy
Lets say I have a composite layer (Top) that is a subclass of CALayer and has any number of children. Top has 2 child layers within it. The first sublayer (A) should always be a fixed width - lets say 100 pixels wide. The second sublayer (B) should be the remainder of the size of Top. Both A and B should occupy the entire height of Top. This is pretty straightforward to code up in layoutSubviews.
Let's presume that Top has no knowledge of A or B. Also presume that Top has a delegate that controls when it should be animated (the delegate provides actionForLayer:forKey: and no other CALayer delegate functions).
I'd like to devise a strategy where for every possible size of Top, the user will always see A and B rendered according to the constraints listed above - even when the size of Top is being animated, even when it is being animated with any variety of animation parameters (durations, functions, offsets, etc).
Just as Top's animations are driven from some containing view or layer through its delegate - it seems that A and B should have their animations setup their containing layer - Top. I want to keep things well-composed, so I don't want the layout of A & B within Top to need to be understood by anything other than Top.
So - the question is what's the best strategy to chain the animations down the layer tree to keep all of the animation parameters in sync?
Here's some sample code that does chaining through the use of actionForLayer:forKey:, but middle function has to go through some fairly involved work (which isn't included) to translate all of the settings from its animation to the sublayer's animation. Not included in this sample is any code that deals with interpolating the values of the bounds. For example, imagine a case where an animation is setup to use a different fromValue, or a keyframe animation. Those values would need to be solved for the sublayers and applied accordingly.
#import "ViewController.h"
#interface MyTopLayer : CALayer
#end
static const CGFloat fixedWidth = 100.0;
#implementation MyTopLayer
-(instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = [[UIColor redColor] CGColor];
CALayer *fixedLayer = [[CALayer alloc] init];
CALayer *slackLayer = [[CALayer alloc] init];
[self addSublayer:fixedLayer];
[self addSublayer:slackLayer];
fixedLayer.anchorPoint = CGPointMake(0,0);
fixedLayer.position = CGPointMake(0,0);
slackLayer.anchorPoint = CGPointMake(0,0);
slackLayer.position = CGPointMake(fixedWidth,0);
fixedLayer.backgroundColor = [[UIColor yellowColor] CGColor];
slackLayer.backgroundColor = [[UIColor purpleColor] CGColor];
//fixedLayer.delegate = self; // no reason to ever animate this layer since it is static
slackLayer.delegate = self;
}
return self;
}
-(id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
if (![event isEqualToString:#"bounds"]) {
return nil;
}
CAAnimation *boundsAnim = [self animationForKey:#"bounds"];
NSLog(#"boundsAnim=%#", boundsAnim);
if (!boundsAnim) {
return (id<CAAction>)[NSNull null];
}
CAAnimation *sublayerBoundsAnim;
if ([boundsAnim isKindOfClass:[CABasicAnimation class]]) {
CABasicAnimation *subAnim = [CABasicAnimation animationWithKeyPath:#"bounds"];
// transform properties, like from, to & by value from boundsAnim (outer) to the inner layer's animation
sublayerBoundsAnim = subAnim;
} else {
CAKeyframeAnimation *subAnim = [CAKeyframeAnimation animationWithKeyPath:#"bounds"];
// copy/interpolate keyframes
sublayerBoundsAnim = subAnim;
}
sublayerBoundsAnim.timeOffset = boundsAnim.timeOffset;
sublayerBoundsAnim.duration = boundsAnim.duration;
sublayerBoundsAnim.timingFunction = boundsAnim.timingFunction;
return sublayerBoundsAnim;
}
-(void)layoutSublayers {
{
CALayer *fixedLayer = [self.sublayers firstObject];
CGRect b = self.bounds;
b.size.width = fixedWidth;
fixedLayer.bounds = b;
}
{
CALayer *slackLayer = [self.sublayers lastObject];
CGRect b = self.bounds;
b.size.width -= fixedWidth;
slackLayer.bounds = b;
}
}
#end
#interface MyView : UIView
#end
#implementation MyView
{
bool _shouldAnimate;
}
+(Class)layerClass {
return [MyTopLayer class];
}
-(instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.layer.delegate = self;
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(doubleTapRecognizer:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
[self addGestureRecognizer:doubleTapRecognizer];
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(tapRecognizer:)];
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
[self addGestureRecognizer:tapRecognizer];
}
return self;
}
CGFloat getRandWidth() {
const static int maxWidth=1024;
const static int minWidth=fixedWidth*1.1;
return minWidth+((((CGFloat)rand())/(CGFloat)RAND_MAX)*(maxWidth-minWidth));
}
-(void)tapRecognizer:(UITapGestureRecognizer*) gr {
_shouldAnimate = true;
CGFloat w = getRandWidth();
self.layer.bounds = CGRectMake(0,0,w,self.layer.bounds.size.height);
}
-(void)doubleTapRecognizer:(UITapGestureRecognizer*) gr {
_shouldAnimate = false;
CGFloat w = getRandWidth();
self.layer.bounds = CGRectMake(0,0,w,self.layer.bounds.size.height);
}
-(id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
if (_shouldAnimate) {
if ([event isEqualToString:#"bounds"]) {
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:event];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
anim.duration = 2.0;
//anim.timeOffset = 0.5;
anim.fromValue = [NSValue valueWithCGRect:CGRectMake(0,0,100,100)];
return anim;
} else {
return nil;
}
} else {
return (id<CAAction>)[NSNull null];
}
}
#end
My question is - does anybody have a better way to get this done? It seems a little bit scary that I've not seen any mention of this sort of hierarchical chaining anywhere. I'm aware that I would probably also need to do some more work on canceling sublayer animations when the top layer's animation is canceled. Relying simply on the currently attached animation, especially w/out concern for the current time that that function is in seems like it could be a source of errors somewhere down the line.
I'm also not sure how well this would perform in the wild since they aren't in the same animation group. Any thoughts there would be greatly appreciated.
I have no idea what is going on.
I am not doing anything special.
I have a UIViewController that I create programmatically, no storyboard.
Then I create a UIView with nothing special
#import "SettingsView.h"
#interface SettingsView()
#property UIImageView* bg;
#end
#implementation SettingsView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.bg = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"happy.png"]];
[self addSubview:self.bg];
}
return self;
}
-(void)layoutSubviews
{
self.bg.frame = self.frame;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
I instantiate it in the VC and when I want to show it I do:
-(void)showSettingsView
{
self.settings.frame = self.view.frame;
self.btnInfo.userInteractionEnabled = NO;
self.btnPause.userInteractionEnabled = NO;
self.btnSettings.userInteractionEnabled = NO;
self.view.userInteractionEnabled = NO;
[self.view addSubview:self.settings];
CGRect frame = self.settings.frame;
frame.origin.x = frame.size.width * 0.48;
frame.origin.y = 0;
self.settings.frame = frame;
}
Instead of seeing the image view roughly half way on the screen, it starts at exactly double (0.48 * 2 = 96%) of the screen.
I cannot understand why coordinates are doubled or something?
When I check the frame, I clearly see the origin at around 150 which is half the width of the iPhone.
What could be happening?
Thanks
Could it be that in...
-(void)layoutSubviews
{
self.bg.frame = self.frame;
}
you should be using bounds instead of frame of your self object...
-(void)layoutSubviews
{
self.bg.frame = self.bounds;
}
I am trying to set the frame size of an UIImageView inside a subclass of UICollectionCell but it does not work. I use storyboard.
In the code, everything works but the last line regarding the imageView.
If i put the code in drawRect it works ok.
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super initWithCoder:decoder]))
{
self.backgroundColor = [UIColor colorWithWhite:0.85f alpha:1.0f];
self.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.layer.borderWidth = 1.0f;
self.layer.cornerRadius = 8.0f;
self.layer.masksToBounds = NO;
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowRadius = 3.0f;
self.layer.shadowOffset = CGSizeMake(0.0f, 2.0f);
self.layer.shadowOpacity = 0.5f;
self.imageView.frame = CGRectMake(10, 10, 50, 50);
}
That is because your imageView (I believe it's and IBOutlet) is not yet initialized, it's still nil. Be aware that some of you variables/properties are being initialized inside init's method. Set you breakpoint and you will see. Try set the imageView values somewhere else such as drawRect (as you suggest) or awakeFromNib.
Is the image view being moved later by a different object? Try moving that code into layoutSubviews
Add this method:
- (void) layoutSubviews
{
[super layoutSubviews];
self.imageView.frame = CGRectMake(10, 10, 50, 50);
}
I'm trying to make a button that will show/hide a color-selection slider. That's the easy part.
What I am not sure how to do is to make the button be a circle with an outline (say black) and the fill be the color shown in the slider. As the user moves the slider, the color of the circle should change accordingly.
I am new to iOS development, so probably I am approaching this all wrong. I would really appreciate any ideas even they are completely different from what I have here.
I started to approach this with a subclass using QuartzCore layers, and the result is decent, but there are issues/things that bother me:
For some reason I get no highlight state when pressing the button
It's obviously odd to use rounded corners to achieve a circle
Unfortunately, it seems at the point were I draw the layers, the button is not laid out yet so I have had to hardcode the radius instead of making it based on the button size.
So, yeah I hardly think this approach is ideal. I will greatly appreciate any and all help. Thank you!
#import "ColorPickerButton.h"
#implementation ColorPickerButton
#pragma mark - UIButton Overrides
+ (ColorPickerButton *)buttonWithType:(UIButtonType)buttonType
{
return [super buttonWithType:UIButtonTypeCustom];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
r = 0.3;
g = 0.6;
b = 0.9;
[self drawOutline];
[self drawColor];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)layoutSubviews
{
_outlineLayer.frame = CGRectInset(self.bounds, 5, 5);
_colorLayer.frame = CGRectInset(self.bounds, 5+_outlineLayer.borderWidth, 5+_outlineLayer.borderWidth);
[super layoutSubviews];
}
#pragma mark - Layer setters
- (void)drawOutline
{
if (!_outlineLayer)
{
_outlineLayer = [CALayer layer];
_outlineLayer.cornerRadius = 20.0;
_outlineLayer.borderWidth = 3;
_outlineLayer.borderColor = [[UIColor colorWithRed:170.0/255.0 green:170.0/255.0 blue:170.0/255.0 alpha:1.0] CGColor];
_outlineLayer.opacity = 0.6;
[self.layer insertSublayer:_outlineLayer atIndex:1];
}
}
- (void)drawColor
{
if (!_colorLayer)
{
_colorLayer = [CALayer layer];
}
_colorLayer.cornerRadius = 16.0;
_colorLayer.backgroundColor = [[UIColor colorWithRed:r green:g blue:b alpha:1.0] CGColor];
_colorLayer.opacity = 1.0;
[self.layer insertSublayer:_colorLayer atIndex:2];
}
- (void)changeColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue
{
r = red;
g = green;
b = blue;
[self drawColor];
}
#end
We do this in our app. We use the CALayer on the button and add rounded corners 1/2 the size of the component width. We add a border to the button and then set the button background color to achieve the 'fill' color. Works well.