getting into the loop when doing progress view, ios - objective-c

I am creating progress view by using CALayer by following
Header class
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface ProgressBar : UIView
#property (strong, nonatomic) CALayer *subLayer;
#property (nonatomic) CGFloat progress;
#end
Implementation class
#implementation ProgressBar
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.progress = 0;
self.backgroundColor = [UIColor whiteColor];
self.subLayer = [CALayer layer];
self.subLayer.frame = self.bounds;
self.subLayer.backgroundColor = [UIColor orangeColor].CGColor;
[self.layer addSublayer:self.subLayer];
}
return self;
}
- (void)setProgress:(CGFloat)value {
NSLog(#"what is going on here");
if (self.progress != value) {
self.progress = value;
[self setNeedsLayout];
}
}
- (void)layoutSubviews {
CGRect rect = [self.subLayer frame];
rect.size.width = CGRectGetWidth([self bounds]) * self.progress;
[self.subLayer setFrame:rect];
}
In my viewcontroller, I am doing
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.pBar = [[ProgressBar alloc] initWithFrame:CGRectMake(0, 50, 320, 10)];
[self.view addSubview:self.pBar];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)click:(id)sender {
[self.pBar setProgress:.8];
}
When (IBAction)click is fired, I am getting into the infinitive loop like the picture below
Does anyone know what I am getting into this loop. Please advice me on this issue. Thanks

The problem is that your setProgress: method calls the setProgress: method - recursion.
- (void)setProgress:(CGFloat)value {
NSLog(#"what is going on here");
if (self.progress != value) {
self.progress = value; // <-- This calls [self setProgress:value]
[self setNeedsLayout];
}
}
Change the bad line to:
_progress = value;
Setting the ivar directly is always required when you implement the setter method of a property.

Related

Animating custom property

I'm trying to animate a custom property, and as I've seen on several sources it seems this would be the way to go, but I'm missing something. Here's the CALayer subclass:
#implementation HyNavigationLineLayer
#dynamic offset;
- (instancetype)initWithLayer:(id)layer
{
self = [super initWithLayer:layer];
if (self) {
HyNavigationLineLayer * other = (HyNavigationLineLayer*)layer;
self.offset = other.offset;
}
return self;
}
-(CABasicAnimation *)makeAnimationForKey:(NSString *)key
{
// TODO
return nil;
}
- (id<CAAction>)actionForKey:(NSString *)event
{
if ([event isEqualToString:#"offset"]) {
return [self makeAnimationForKey:event];
}
return [super actionForKey:event];
}
+ (BOOL)needsDisplayForKey:(NSString *)key
{
if ([key isEqualToString:#"offset"]) {
return YES;
}
return [super needsDisplayForKey:key];
}
- (void)drawInContext:(CGContextRef)ctx
{
NSLog(#"Never gets called");
}
#end
I believe this is the only relevant method on my view:
#implementation HyNavigationLineView
+ (Class)layerClass
{
return [HyNavigationLineLayer class];
}
#end
And, finally, in my view controller:
- (void)viewDidLoad
{
[super viewDidLoad];
// Instantiate the navigation line view
CGRect navLineFrame = CGRectMake(0.0f, 120.0f, self.view.frame.size.width, 15.0f);
self.navigationLineView = [[HyNavigationLineView alloc] initWithFrame:navLineFrame];
// Make it's background transparent
self.navigationLineView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.0f];
self.navigationLineView.opaque = NO;
[[self.navigationLineView layer] addSublayer:[[HyNavigationLineLayer alloc] init]];
[self.view addSubview:self.navigationLineView];
}
The thing is that the drawInContext method is not called at all, although layerClass is. Am I missing something to make the layer draw?
Solved it. Need to call setNeedsDisplay
- (void)viewDidLoad
{
[super viewDidLoad];
// Instantiate the navigation line view
CGRect navLineFrame = CGRectMake(0.0f, 120.0f, self.view.frame.size.width, 15.0f);
self.navigationLineView = [[HyNavigationLineView alloc] initWithFrame:navLineFrame];
// Make it's background transparent
self.navigationLineView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.0f];
self.navigationLineView.opaque = NO;
[[self.navigationLineView layer] addSublayer:[[HyNavigationLineLayer alloc] init]];
[self.view addSubview:self.navigationLineView];
[self.navigationLineView.layer setNeedsDisplay];
}

Cant call methods on instance

While working on a project i made a class i instantiate in another class (nothing special) but if i try to call a method for this instance i get this:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CALayer animate]: unrecognized selector sent to instance
here is the class .h :
#import <QuartzCore/QuartzCore.h>
#interface SFStripe : CALayer {
NSTimer *animation;
}
- (id)initWithXPosition:(NSInteger)positionX yPosition:(NSInteger)positionY;
- (id)initEndedWithXPosition:(NSInteger)positionX yPosition:(NSInteger)positionY;
- (void)animate;
- (void)reduceSize;
- (void)logSomething;
#property NSTimer *animation;
#end
and here the .m :
#import "SFStripe.h"
#implementation SFStripe
#synthesize animation;
- (id)initWithXPosition:(NSInteger)positionX yPosition:(NSInteger)positionY {
self = [CALayer layer];
self.backgroundColor = [UIColor blackColor].CGColor;
self.frame = CGRectMake(positionX, positionY, 5, 20);
self.cornerRadius = 5;
return self;
}
- (id)initEndedWithXPosition:(NSInteger)positionX yPosition:(NSInteger)positionY {
self = [CALayer layer];
self.backgroundColor = [UIColor grayColor].CGColor;
self.frame = CGRectMake(positionX, (positionY + 7.5), 5, 5);
self.cornerRadius = 2;
self.delegate = self;
return self;
}
- (void) logSomething {
NSLog(#"It did work!");
}
- (void)reduceSize {
if (self.frame.size.width > 0 && self.frame.size.height >= 5) {
self.frame = CGRectMake(self.frame.origin.x, (self.frame.origin.y - 0.5), self.frame.size.width, (self.frame.size.height - 0.5));
[self setNeedsDisplay];
NSLog(#"Width: %d", (int)self.frame.size.width);
NSLog(#"Height: %d", (int)self.frame.size.height);
} else {
self.backgroundColor = [UIColor grayColor].CGColor;
self.frame = CGRectMake(self.frame.origin.x, (self.frame.origin.y + 7.5), 5, 5);
[animation invalidate];
}
}
- (void)animate {
animation = [NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:#selector(reduceSize) userInfo:nil repeats:YES];
}
#end
i imported this class into a fresh project just to see if it is working there but get the same error. here is how i call one of the methods for an instance of the class (i get the same error for all of the methods)
in the mainview.h (of the clean project):
#import <UIKit/UIKit.h>
#import "SFStripe.h"
#import <QuartzCore/QuartzCore.h>
#interface STViewController : UIViewController {
SFStripe *stripe;
}
- (IBAction)animate:(id)sender;
#end
i created the instance and in the .m i let the action call the method for the instance:
#import <QuartzCore/QuartzCore.h>
#import "STViewController.h"
#import "SFStripe.h"
#interface STViewController ()
#end
#implementation STViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
stripe = [[SFStripe alloc] initWithXPosition:30 yPosition:30];
[self.view.layer addSublayer:stripe];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)animate:(id)sender {
[stripe animate];
}
#end
sorry for all the code but i cant find an answer for this problem that helps and hope someone can help me!
This is not the correct way to write an initializer:
- (id)initWithXPosition:(NSInteger)positionX yPosition:(NSInteger)positionY
{
self = [CALayer layer];
...
You're assigning self to be a plain old CALayer instance, rather than an instance of your subclass.
Use self = [super init] instead, and then check that self is not nil.

UITextField shadow does not display when editing

I want to draw the text in an UITextField with a shadow. In order to do this, I have subclassed UITextField, and implemented the drawTextInRect: method as follows:
- (void)drawTextInRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// Create shadow color
float colorValues[] = {0.21875, 0.21875, 0.21875, 1.0};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorRef shadowColor = CGColorCreate(colorSpace, colorValues);
CGColorSpaceRelease(colorSpace);
// Create shadow
CGSize shadowOffset = CGSizeMake(2, 2);
CGContextSetShadowWithColor(context, shadowOffset, 0, shadowColor);
CGColorRelease(shadowColor);
// Render text
[super drawTextInRect:rect];
}
This works great for when the text field is not editing, but as soon as editing begins, the shadow disappears. Is there anything I am missing?
Here is the code for following component
#interface AZTextField ()
- (void)privateInitialization;
#end
#implementation AZTextField
static CGFloat const kAZTextFieldCornerRadius = 3.0;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (!self) return nil;
[self privateInitialization];
return self;
}
// In case you decided to use it in a nib
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) return nil;
[self privateInitialization];
return self;
}
- (void)privateInitialization
{
self.borderStyle = UITextBorderStyleNone;
self.layer.masksToBounds = NO;
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
self.layer.shadowOpacity = 0.5f;
self.layer.backgroundColor = [UIColor whiteColor].CGColor;
self.layer.cornerRadius = 4;
// This code is better to be called whenever size of the textfield changed,
// so if you plan to do that you can add an observer for bounds property
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:kAZTextFieldCornerRadius];
self.layer.shadowPath = shadowPath.CGPath;
}
#end
Couple of things to consider:
You want to set borderStyle to none, otherwise you'll end up with
UIKit putting subviews into your textfield
Depending on the Xcode
version you might want to link QuartzCore and to #import
<QuartzCore/QuartzCore.h>
For more complex appearance you can still
use shadow properties of the layer and move the drawing code itself
into the drawRect: method, but jake_hetfield was right if you
override drawRect you don't want to call super, especially in the end
of the method
As for the text drawing (you can see that it sticks to
close to the component borders), you have a separate
drawTextInRect: and drawPlaceholderInRect: method that draws the
text and placeholder respectively
You can use UIColor method for
colors and call CGColor property, it makes code more readable and
easier to maintain
Hope that helps!
Inspired by #jake_hetfield answer I created a custom UITextField that uses an internal label to do the drawing, check it out:
ShadowTextField .h file
#import <UIKit/UIKit.h>
#interface ShadowTextField : UITextField
// properties to change the shadow color & offset
#property (nonatomic, retain) UIColor *textShadowColor;
#property (nonatomic) CGSize textShadowOffset;
- (id)initWithFrame:(CGRect)frame
font:(UIFont *)font
textColor:(UIColor *)textColor
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGSize)shadowOffset;
#end
ShadowTextField .m file
#import "ShadowTextField.h"
#interface ShadowTextField ()
#property (nonatomic, retain) UILabel *internalLabel;
#end
#implementation ShadowTextField
#synthesize internalLabel = _internalLabel;
#synthesize textShadowColor = _textShadowColor;
#synthesize textShadowOffset = _textShadowOffset;
- (id)initWithFrame:(CGRect)frame
font:(UIFont *)font
textColor:(UIColor *)textColor
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGSize)shadowOffset
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
// register to my own text changes notification, so I can update the internal label
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleUITextFieldTextDidChangeNotification)
name:UITextFieldTextDidChangeNotification
object:nil];
self.font = font;
self.textColor = textColor;
self.textShadowColor = shadowColor;
self.textShadowOffset = shadowOffset;
}
return self;
}
// when the user enter text we update the internal label
- (void)handleUITextFieldTextDidChangeNotification
{
self.internalLabel.text = self.text;
[self.internalLabel sizeToFit];
}
// init the internal label when first needed
- (UILabel *)internalLabel
{
if (!_internalLabel) {
_internalLabel = [[UILabel alloc] initWithFrame:self.bounds];
[self addSubview:_internalLabel];
_internalLabel.font = self.font;
_internalLabel.backgroundColor = [UIColor clearColor];
}
return _internalLabel;
}
// override this method to update the internal label color
// and to set the original label to clear so we wont get two labels
- (void)setTextColor:(UIColor *)textColor
{
[super setTextColor:[UIColor clearColor]];
self.internalLabel.textColor = textColor;
}
// override this method to update the internal label text
- (void)setText:(NSString *)text
{
[super setText:text];
self.internalLabel.text = self.text;
[self.internalLabel sizeToFit];
}
- (void)setTextShadowColor:(UIColor *)textShadowColor
{
self.internalLabel.shadowColor = textShadowColor;
}
- (void)setTextShadowOffset:(CGSize)textShadowOffset
{
self.internalLabel.shadowOffset = textShadowOffset;
}
- (void)drawTextInRect:(CGRect)rect {
// don't draw anything
// we have the internal label for that...
}
- (void)dealloc {
[_internalLabel release];
[_textShadowColor release];
[super dealloc];
}
#end
Here is how you use it in your view controller
- (void)viewDidLoad
{
[super viewDidLoad];
ShadowTextField *textField = [[ShadowTextField alloc] initWithFrame:CGRectMake(0, 0, 320, 30)
font:[UIFont systemFontOfSize:22.0]
textColor:[UIColor whiteColor]
shadowColor:[UIColor redColor]
shadowOffset:CGSizeMake(0, 1) ] ;
textField.text = #"This is some text";
textField.backgroundColor = [UIColor blackColor];
[self.view addSubview:textField];
}
You could try to do the drawing of the label yourself. Remove
[super drawTextInRect:rect]
And instead draw your own label. I haven't tried this but it could look something like this:
// Declare a label as a member in your class in the .h file and a property for it:
UILabel *textFieldLabel;
#property (nonatomic, retain) UILabel *textFieldLabel;
// Draw the label
- (void)drawTextInRect:(CGRect)rect {
if (self.textFieldLabel == nil) {
self.textFieldLabel = [[[UILabel alloc] initWithFrame:rect] autorelease];
[self.view addSubview:myLabel];
}
self.textFieldLabel.frame = rect;
self.textFieldLabel.text = self.text;
/** Set the style you wish for your label here **/
self.textFieldLabel.shadowColor = [UIColor grayColor];
self.textFieldLabel.shadowOffset = CGSizeMake(2,2);
self.textFieldLabel.textColor = [UIColor blueColor];
// Do not call [super drawTextInRect:myLabel] method if drawing your own text
}
Stop calling super and render the text yourself.
Have your tried with the standard shadow properties of the CALayer? it usually is enough and it is lot simpler. Try something like this with a regular UITextField:
self.inputContainer.layer.shadowColor=[UIColor blackColor].CGColor;
self.inputContainer.layer.shadowRadius=8.0f;
self.inputContainer.layer.cornerRadius=8.0f;
self.inputContainer.layer.shadowOffset=CGSizeMake(0, 4);
You need to import QuartzCore first of course!
#import <QuartzCore/QuartzCore.h>

Subclass of UIView. How to add through Interface Builder?

I have a subclass of UITextField and want to be able to add it to the view in IB. What I did is added UITextField and changed its class to my subclass in Identity tab of Inspector. But I can only see UITextField on the view.
Code:
#interface ExtendedTextField : UITextField {
}
//implementation
#implementation ExtendedTextField
- (void)baseInit
{
UIImage * curImage = [UIImage imageNamed:#"tfbg.png"];
[self baseInitWithImage : curImage];
}
- (void)baseInitWithImage : (UIImage *) aImage
{
aImage = [aImage stretchableImageWithLeftCapWidth:29 topCapHeight:29];
self.background = aImage;
self.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
UIView * curLeftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, self.frame.size.height)];
self.leftView = curLeftView;
self.rightView = curLeftView;
[curLeftView release];
self.leftViewMode = UITextFieldViewModeAlways;
self.rightViewMode = UITextFieldViewModeAlways;
[self setTextColor:[UIColor greenColor]];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self baseInit];
}
/*
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self baseInit];
}
return self;
}
*/
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
{
[self baseInit];
}
return self;
}
- (id)initWithFrame:(CGRect)frame withImage:(UIImage*)aImage
{
self = [super initWithFrame:frame];
if (self) {
[self baseInitWithImage : aImage];
}
return self;
}
- (void)dealloc {
[super dealloc];
}
#end
EDIT: I noticed several things:
-- if I put [super initWithFrame:...] instead of [super initWithCoder:...] inside
(id)initWithCoder:(NSCoder *)aDecoder
it works well.
-- Now I am using awakefromnib instead of initwithcoder and the only thing that get changed in textfield is textColor.
Could someone explain why so?
Solution I needed to set border style to none. BS overlayed bg image.
While showing your .xib in Xcode, reveal the right pane, select your view and in the third tab, change Classto whatever you want it to be.

How to use drawRect to draw in a existing view?

I'm doing a GPS tracking app. Every time it receives a Latitude/Longitude it converts it to (x,y) coordinates and calls drawRect to draw a line between two (x,y) pairs.
However, the drawRect method just clear all the old contents before it draw new thing. How I can make the drawRect to draw new thing on the existing canvas? Thanks in advance
here is my viewController.h
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#class routetrack;
#interface simpleDrawViewController : UIViewController <CLLocationManagerDelegate> {
CLLocationManager *locationManager;
IBOutlet routetrack *routeroute;
float latOld;
float longOld;
float latNew;
float longNew;
}
- (IBAction) gpsValueChanged;
#property (nonatomic,retain) CLLocationManager *locationManager;
#property (retain,nonatomic) routetrack *routeroute;
ViewController.m
#import "simpleDrawViewController.h"
#import "routetrack.h"
#implementation simpleDrawViewController
#synthesize locationManager,btn,routeroute;
- (IBAction) gpsValueChanged{
[routeroute setLocationX:longNew setLocationY:latNew
oldLocation:longOld oldLocationY:latOld ];
[routeroute setNeedsDisplay];
}
- (void)viewDidLoad {
[super viewDidLoad];
if (nil == locationManager)
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void) locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
if (oldLocation) {
latOld=oldLocation.coordinate.latitude;
longOld=oldLocation.coordinate.longitude;
latNew=newLocation.coordinate.latitude;
longNew=newLocation.coordinate.longitude;
}
else {
latOld=53.3834;
longOld=-6.5876;
latNew=53.3835;
longNew=-6.5877;
}
[self gpsValueChanged];
}
- (void) locationManager:(CLLocationManager *)manager didFailwithError:
(NSError *)error
{
if([error code] == kCLErrorDenied)
[locationManager stopUpdatingLocation];
NSLog(#"location manger failed");
}
- (void)dealloc {
[locationManager release];
[super dealloc];
}
#end
my drawing class, a subclass of UIView
#import "routetrack.h"
#implementation routetrack
- (void) setLocationX:(float) loX setLocationY:(float) loY
oldLocation:(float) oldX oldLocationY:(float) oldY {
self->locationX = loX;
self->locationY = loY;
self->oldLoX = oldX;
self->oldLoY = oldY;
scaleX= 36365.484375;
scaleY= 99988.593750;
maxLat= 53.3834;
maxLog= -6.5976;
drawX = locationX;
drawY = locationY;
tempX=(maxLog - drawX) * scaleX;
tempY=(maxLat - drawY) * scaleY;
lastX = (int) tempX;
lastY = (int) tempY;
drawX = oldLoX;
drawY = oldLoY;
tempX=(maxLog - drawX) * scaleX;
tempY=(maxLat - drawY) * scaleY;
startX = (int) tempX;
startY = (int) tempY;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)drawRect:(CGRect)rect {
int firstPointX = self->startX;
int firstPointY = self->startY;
int lastPointX = self->lastX;
int lastPointY = self->lastY;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextSetLineWidth(context, 2.0);
CGContextMoveToPoint(context, firstPointX, firstPointY);
CGContextAddLineToPoint(context, lastPointX , lastPointY);
CGContextStrokePath(context);
}
UIView does not have a canvas model. If you want to keep a canvas, you should create a CGLayer or a CGBitmapContext and draw onto that. Then draw that in your view.
I would create an ivar for a CGLayerRef, and then drawRect: would look something like this (untested):
- (void)drawRect:(CGRect)rect {
if (self.cgLayer == NULL) {
CGLayerRef layer = CGLayerCreateWithContext(UIGraphicsContextGetCurrent(), self.bounds, NULL);
self.cgLayer = layer;
CGLayerRelease(layer);
}
... various Core Graphics calls with self.cgLayer as the context ...
CGContextDrawLayerInRect(UIGraphicsContextGetCurrent(), self.bounds, self.cgLayer);
}
Whenever you wanted to clear your canvas, just self.cgLayer = NULL.
setCgLayer: would be something like this:
- (void)setCgLayer:(CGLayerRef)aLayer {
CGLayerRetain(aLayer);
CGLayerRelease(cgLayer_);
cgLayer_ = aLayer;
}
What exactly to you mean by "old contents"? If you want to draw a line from your GPS data, you have to draw all points every time in the implementation of drawRect.