Reduce Core-Graphics memory consumption on iphone - iphone

I am writing a program which redraws a graph every so often. Unfortunately it seems that for some reason core graphics does not seem to release the previous graphic so I run into memory problems fast. How can I force it to release memory at the end of each draw?
Not that this is not a memory leak as the memory does get released when I leave close that view. I have tried moving the NSTimer to several places in the code to no avail.
Here is the code below with the files going from most important to (potentially) irrelevant.
First is the core file+header which draws the graphic (in this case a sine wave) every so often.
GraphicsView.h
#import <UIKit/UIKit.h>
#import "Model.h"
#interface GraphicsView : UIView
{
Model * model;
}
#end
GraphicsView.m
#import "GraphicsView.h"
#implementation GraphicsView
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
model=[Model sharedModel];
[NSTimer scheduledTimerWithTimeInterval:.010 target:self selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
int now=[model n];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, CGRectMake(0, 0, rect.size.width, rect.size.height));
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextMoveToPoint(context, 0.0, 0.0);
double xc_old=0;
double yc_old=100;
for (int i=0;i<now;i++)
{
double xc=(double)i/(double)now*200;
double yc=100-100*sin((double)i/6);
CGContextMoveToPoint(context, xc_old, yc_old);
CGContextAddLineToPoint(context, xc, yc);
CGContextStrokePath(context);
xc_old=xc;
yc_old=yc;
}
}
- (void)dealloc {
[super dealloc];
}
#end
The view controller header:
#import <UIKit/UIKit.h>
#import "GraphicsView.h"
#interface SbarLeakAppDelegate : NSObject <UIApplicationDelegate>
{
UIWindow *window;
Model *model;
GraphicsView * gv;
NSTimer * timer;
}
#end
The view controller itself.
#import "SbarLeakAppDelegate.h"
#implementation SbarLeakAppDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
model=[Model sharedModel];
CGRect appRect;
CGRect frame = CGRectInset(appRect, 0.0f, 0.0f);
appRect.origin=CGPointMake(0.0, 40.0);
appRect.size = CGSizeMake(200.0f, 200.0f);
frame = CGRectInset(appRect, 0.0f, 0.0f);
gv=[[GraphicsView alloc] initWithFrame:appRect];
[gv setFrame:frame];
[window addSubview:gv];
[gv release];
[window makeKeyAndVisible];
}
-(void) viewDidAppear:(id)sender
{
//timer=[NSTimer scheduledTimerWithTimeInterval:.250 target:gv selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
}
- (void)dealloc
{
[window release];
[super dealloc];
}
#end
This code is only included for completeness but (should not) affect the question.
The data source header file (just a simple integer)
#import <UIKit/UIKit.h>
#interface Model : NSObject
{
int n;
}
#property int n;
+(Model *) sharedModel;
-(void) inc;
#end
The data source, Model.m:
#import "Model.h"
#implementation Model
static Model * sharedModel = nil;
+ (Model *) sharedModel
{
if (sharedModel == nil)
sharedModel = [[self alloc] init];
return sharedModel;
}
#synthesize n;
-(id) init
{
self=[super init];
[NSTimer scheduledTimerWithTimeInterval:.05 target:self selector:#selector(inc) userInfo:nil repeats:YES];
return self;
}
-(void) inc
{
n++;
}
#end

The selector you use for your timer does not have the correct signature.
From the Apple docs:
The selector must have the following signature:
- (void)timerFireMethod:(NSTimer*)theTimer
You should use your own method for the timer and call setNeedsDisplay from there. As Robert said, you should turn down the frequency a bit, but I don't think either of these issues will solve your problem.

Did you try: self.clearsContextBeforeDrawing = YES; in the drawRect method ?

According to the NSTimer documentation, the max time resolution is 50-100ms... it looks like you're trying for 1ms... according to spec a timer that asks for finer resolution than that shouldn't cause memory issues, it just shouldn't fire as often as needed. I suspect that's part of your problem, though.
NSTimer Class Reference
Because of the various input sources a
typical run loop manages, the
effective resolution of the time
interval for a timer is limited to on
the order of 50-100 milliseconds.

+ (Model *) sharedModel
{
if (sharedModel == nil)
sharedModel = [[self alloc] init];
return sharedModel;
}
in this snippet, autorelease is missing. This also leads to a leak i think.
try with
sharedModel = [[[self alloc] init] autorelease];

Related

How to attribute parameters to a subclass in C4

I'm trying to create a subclass object, whose size can be determined when I declare it. For example, do something near to "circle (int width, int height)", and in the C4WorkSpace, attribute two numbers that define the size of the circle. If I understood correctly, you can use initializers for that, like this one:
- (id) initWithNumber: (int) n {
self = [super init]; ❶ ❷
if (self) {
self->_number = n; ❸
}
return self; ❹
}
...but I didn't quite understand how to use it and where to put it.
Here is the code I'm working with. I inserted "size" into the parameters of the ellipse, just to illustrate what I'm trying to do.
My circle.h file:
#import "C4Shape.h"
#interface circle : C4Shape
#end
And the circle.m one:
#import "circle.h"
#implementation circle
-(void) setup
{
[self addGesture:PAN name:#"pan" action:#"move:"];
[self addGesture:TAP name:#"tap" action:#"changeColour"];
[self ellipse:CGRectMake(0, 0, size, size)];
[self setFillColor:[UIColor blackColor]];
[self setStrokeColor:[UIColor blackColor]];
}
-(void) changeColour
{
self.fillColor = [UIColor colorWithRed:[C4Math randomInt: 100]/100.0f green:[C4Math randomInt: 100]/100.0f blue:[C4Math randomInt: 100]/100.0f alpha:1.0f];
}
#end
How is the best way to attribute a variable to a subclass in C4, in this case? If possible, could you explain how I create the object in the C4WorkSpace.m too?
Thanks for your attention. And sorry if I was not clear.
You can do this. You have to declare your initializer method into the header file in order for other files to see it. You'll need to create an instance variable called size and set it to your number. Alternatively, you could use a property. You provide the definition in your Cirlce.m file. I've changed self->size to be just size, since it is an instance variable in your class.
In your C4Workspace.m you'll need to import the header file, then you'll be able to create one of your objects anywhere in the file. You'll need to call alloc and then your initWithNumber in order to create the object. You'll have to call setup in order to get it to show up on the screen, since that is where you've provided all of your code.
Check out C4: Add panning to an object other than "self" for a related discussion.
Circle.h
#import "C4Shape.h"
#interface Circle : C4Shape
- (id) initWithNumber: (int) n;
#end
Circle.m
#import "Circle.h"
#implementation Circle
{
int size;
}
- (id) initWithNumber: (int) n {
self = [super init];
if (self) {
size = n;
}
return self;
}
-(void) setup
{
[self addGesture:PAN name:#"pan" action:#"move:"];
[self addGesture:TAP name:#"tap" action:#"changeColour"];
[self ellipse:CGRectMake(0, 0, size, size)];
[self setFillColor:[UIColor blackColor]];
[self setStrokeColor:[UIColor blackColor]];
}
-(void) changeColour
{
self.fillColor = [UIColor colorWithRed:[C4Math randomInt: 100]/100.0f green:[C4Math randomInt: 100]/100.0f blue:[C4Math randomInt: 100]/100.0f alpha:1.0f];
}
#end
C4Workspace.m
#import "C4Shape.h"
#import "C4WorkSpace.h"
#import "Circle.h"
#implementation C4WorkSpace
{
Circle * c;
}
-(void)setup
{
c = [[Circle alloc] initWithNumber:100];
[c setup];
[self.canvas addSubview:c];
}
#end

Can't get setNeedsDisplay not working with my custom view to show changes

I'm trying to create a CGRect inside of a custom view (rectView) that will move up and down as you move a slider.
My slider's IBAction calls the following method: (which get's called fine)
- (void)moveRectUpOrDown:(int)y
{
self.verticalPositionOfRect += y;
[self setNeedsDisplay];
}
My drawRect method:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat size = 100;
self.rect = CGRectMake((self.bounds.size.width / 2) - (size / 2),
self.verticalPositionOfRect - (size / 2),
size,
size);
CGContextAddRect(context, self.rect);
CGContextFillPath(context);
}
My custom view's initWithFrame calls the drawRect method using setNeedsDisplay, but for some reason the moveRectUpOrDown won't call drawRect.
Any ideas what I'm doing wrong?
For clarity the entire implementation is below:
//ViewController.h
#import <UIKit/UIKit.h>
#import "rectView.h"
#interface ViewController : UIViewController
#property (strong, nonatomic) IBOutlet rectView *rectView;
- (IBAction)sliderChanged:(id)sender;
#end
//ViewController.m
#import "ViewController.h"
#implementation ViewController
#synthesize rectView;
- (void)viewDidLoad
{
[super viewDidLoad];
self.rectView = [[rectView alloc] initWithFrame:self.rectView.frame];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (IBAction)sliderChanged:(id)sender
{
UISlider *slider = sender;
CGFloat sliderValue = slider.value;
[self.rectView moveRectUpOrDown:sliderValue];
}
#end
//rectView.h
#import <UIKit/UIKit.h>
#interface rectView : UIView
- (void)moveRectUpOrDown:(int)y;
#end
//rectView.m
#import "rectView.h"
#interface rectView ()
#property CGRect rect;
#property int verticalPositionOfRect;
#end
#implementation rectView
#synthesize rect, verticalPositionOfRect;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.verticalPositionOfRect = (self.bounds.size.height / 2);
[self setNeedsDisplay];
}
return self;
}
- (void)moveRectUpOrDown:(int)y
{
self.verticalPositionOfRect += y;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat size = 100.0;
self.rect = CGRectMake((self.bounds.size.width / 2) - (size / 2),
self.verticalPositionOfRect - (size / 2),
size,
size);
CGContextAddRect(context, self.rect);
CGContextFillPath(context);
}
#end
Thanks for the help :)
OK so the answer is the view is never actually added to the viewController's view..
You have an IBOutlet to your rectView - so I assume you dragged a UIView component onto the view in Interface Builder and then changed it's class to rectView, which is all fine BUT in viewDidLoad you wipe over this object with a new one
self.rectView = [[rectView alloc] initWithFrame:self.rectView.frame];
So this is now no longer the object that was loaded from the xib.
Subsequently this view is never actually added to the view hierarchy so it's never going to draw itself as it does not make sense.
So the solution is to remove the line I highlighted above.
Notes
I would probably still go with my other answer for such an implementation but it's helpful to get tripped up and learn new things.
rectView does not follow naming conventions. Ideally you should start your class name with a 2-3 letter prefix (possibly your initial's or company name) followed by a camel cased named.
I don't see how you initialize CGFloat size. Can you put a value in there and see how it works? The rest of the code looks okay to me.
An easier thing would be to just add a UIView and animate this around.
Add an ivar for this UIView
// .h
#property (nonatomic, strong) UIView *animatedView;
// .m
#synthesize animatedView = _animatedView;
then just replace your logic with this
- (void)moveRectUpOrDown:(int)y;
{
CGRect frame = self.animatedView.frame;
frame.origin.y += y;
// If you want the change to animate uncomment this
// [UIView animateWithDuration:0.25f
// animations:^{
// self.animatedView.frame = frame;
// }];
// If you don't want the change to animate uncomment this
// self.animatedView.frame = frame;
}

Text getting blur on zoom-in

I have requirement in which I have tableview as a subview of imageview. Now when the user zooms-in , the text inside the tableview is getting blur. Is there any way to manipulate it at time of zoom-in?
Please follow the steps which is below.
Don't set the position of image statically i mean by giving coordinate.Set the position dynamically or at the run time it will take position based on current window or zoom in zoom out position label,i am not pretty much sure because i havn't faced this situation earlier but as i think it might be use case scenario for solving your problem.
Thanks
I have achieved it by subclassing CALayer for labels. Thanks all for help.
Subclass label as follows :
TiledLable.h file ::
#import <UIKit/UIKit.h>
#import "FastCATiledLayer.h"
#interface TiledLable : UILabel
{
}
#end
TiledLale.m file ::
#import "TiledLable.h"
#import <QuartzCore/QuartzCore.h>
#implementation TiledLable
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// Initialization code.
FastCATiledLayer *tiledLayer = (FastCATiledLayer *)[self layer];
tiledLayer.levelsOfDetail = 2;
tiledLayer.levelsOfDetailBias = 2;
tiledLayer.tileSize = CGSizeMake(1024.0,1024.0);
tiledLayer.contents = nil;
self.layer.contents = nil;
}
return self;
}
// Set the layer's class to be CATiledLayer.
+ (Class)layerClass
{
return [FastCATiledLayer class];
}
- (void)dealloc
{
((CATiledLayer *)[self layer]).delegate = nil;
[self removeFromSuperview];
[self.layer removeFromSuperlayer];
[super dealloc];
}
#end

InitWithFrame not executed if view is a property

I have GraphicView class that inherits from UIView. Its initWithFrame method is:
#implementation GraphicsView
- (id)initWithFrame:(CGRect)frameRect
{
self = [super initWithFrame:frameRect];
// Create a ball 2D object in the upper left corner of the screen
// heading down and right
ball = [[Object2D alloc] init];
ball.position = [[Point2D alloc] initWithX:0.0 Y:0.0];
ball.vector = [[Vector2D alloc] initWithX:5.0 Y:4.0];
// Start a timer that will call the tick method of this class
// 30 times per second
timer = [NSTimer scheduledTimerWithTimeInterval:(1.0/30.0)
target:self
selector:#selector(tick)
userInfo:nil
repeats:YES];
return self;
}
Using Interface Builder I've added a UIView (class = GraphicView) to ViewController.xib. And I've added GraphicView as a property:
#interface VoiceTest01ViewController : UIViewController {
IBOutlet GraphicsView *graphView;
}
#property (nonatomic, retain) IBOutlet GraphicsView *graphView;
- (IBAction)btnStartClicked:(id)sender;
- (IBAction)btnDrawTriangleClicked:(id)sender;
#end
But with this code doesn't work, I need to call [graphView initWithFrame:graphView.frame] to make it works.
- (void)viewDidLoad {
[super viewDidLoad];
isListening = NO;
aleatoryValue = 10.0f;
// Esto es necesario para inicializar la vista
[graphView initWithFrame:graphView.frame];
}
Am I doing well? Is there a better way to do that?
I don't know why initWitFrame is not called if I add GraphicView as a property.
initWithFrame is not called when loading from a NIB, it's initWithCoder instead.
If you may be using both loading from a NIB and programmatic creation, you should make a common method (initCommon maybe?) that you would call from both initWithFrame and initWithCoder.
Oh, and your init method is not using the recommended practices:
- (id)initWithFrame:(CGRect)frameRect
{
if (!(self = [super initWithFrame:frameRect]))
return nil;
// ...
}
You should always check the return value of [super init...].

UIView not retaining subviews

I made this play/pause/stop control but it doesn't work as expected. at load time it creates three views and stores them in an array, each view represents one state of the control. In the stopped state it contains a play button that is a member of a simple class cluster I made. In the other two states it contains a UIView with two of the buttons as subviews. In the first state it works and does exactly what it's supposed to, but when it tries to go to the next state it looks in the array and finds views at the playing state and paused state positions with no subviews. In fact, if you trace it through the execution of the loadView function the array never gets a view with subviews in it even though I called addSubview:(UIView *)view which the documentation says this about: This method retains view and sets its next responder to the receiver, which is its new superview.
I would really like some help trying to understand why this is happening. To be more clear, why don't the UIViews that are passed to the array have subviews when the local variables for them do.
Thanks in advance,
Rich
Here's the source:
// IMSpeechControl.h
#import <UIKit/UIKit.h>
#import "IMSpeechEngine.h"
typedef enum {
IMSpeechControlStateStopped = 0,
IMSpeechControlStatePlaying = 1,
IMSpeechControlStatePaused = 2
} IMSpeechControlState;
/* State Stopped: speech control should show a Play button.
State Playing: speech control should show a Pause button and a Stop button.
State Paused : speech control should show a Play button and a Stop button.
*/
#class IMSpeechControl;
#protocol IMSpeechControlDelegate <NSObject>
- (NSString *)speechControlNeedsText:(IMSpeechControl *)sender;
#end
#interface IMSpeechControl : UIViewController {
IMSpeechControlState controlState;
id delegate;
IMSpeechEngine *speechEngine;
NSMutableArray *controlButtons_;
CGRect frame_;
}
#property (nonatomic, readonly) IMSpeechControlState controlState;
#property (nonatomic, assign) id<IMSpeechControlDelegate> delegate;
// Designated initilazer
- (IMSpeechControl *)initWithFrame:(CGRect)frame;
// This must be here, do not call it from outside it's control buttons
- (void)changeToState:(IMSpeechControlState)controlState;
- (void)play;
- (void)pause;
- (void)stop;
#end
This is the important one.
// IMSpeechControl.m
#import "IMSpeechControl.h"
#import "IMSpeechControlButton.h"
#interface IMSpeechControl ()
#property (nonatomic, assign) IMSpeechEngine *speechEngine;
#property (nonatomic, retain) NSMutableArray *controlButtons;
// Used only for initialization, do not change after calling initWithFrame
// to change the view size after creation
#property (nonatomic) CGRect frame;
- (void)speechEngineDidFinishSpeaking:(NSNotification *)notifictation;
#end
#implementation IMSpeechControl
#synthesize controlState, delegate, speechEngine, frame=frame_;
#synthesize controlButtons=controlButtons_;
/* State Stopped: speech control should show a Play button.
State Playing: speech control should show a Pause button and a Stop button.
State Paused : speech control should show a Play button and a Stop button.
*/
- (IMSpeechControl *)initWithFrame:(CGRect)aFrame {
if (self = [super init]) {
self.frame = aFrame;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(speechEngineDidFinishSpeaking:) name:kDidFinishSpeakingNotificationName object:self.speechEngine];
}
return self;
}
- (void)loadView {
// Initialization code.
// Create the main view.
UIView *aView = [[UIView alloc] initWithFrame:self.frame];
self.view = aView;
[aView release];
// Create the sub-views and store them in an array.
NSMutableArray *controls = [[NSMutableArray alloc] initWithCapacity:3];
// The stopped state play button view can be used directly since it is the only button shown.
IMSpeechControlButton *button = [[IMSpeechControlButton alloc] initWithFrame:self.frame forControl:self style:IMSpeechControlButtonStylePlay];
[controls insertObject:button atIndex:(NSUInteger)IMSpeechControlStateStopped];
[button release];
// The other two states require two buttons each, so the two buttons must be grouped into a UIView that can be easily switched out.
// Make two frames, one for the left and one for the right. Both are half the width of the main view
// The one on the left has the same origin as the main view...
CGRect halfFrameLeft = CGRectMake(frame_.origin.x, frame_.origin.y, frame_.size.width / 2, frame_.size.height);
// and the one on the right starts half-way across the main view
CGRect halfFrameRight = CGRectMake((frame_.origin.x + (frame_.size.width / 2)), frame_.origin.y, frame_.size.width / 2, frame_.size.height);
// Playing state
// Pause button
UIView *playingState = [[UIView alloc] initWithFrame:self.frame];
IMSpeechControlButton *plsPauseButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameLeft forControl:self style:IMSpeechControlButtonStylePause];
[playingState addSubview:plsPauseButton];
[plsPauseButton release];
// Stop button
IMSpeechControlButton *plsStopButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameRight forControl:self style:IMSpeechControlButtonStyleStop];
[playingState addSubview:plsStopButton];
[plsStopButton release];
[controls insertObject:playingState atIndex:(NSUInteger)IMSpeechControlStatePlaying];
[playingState release];
// Paused state
// Play button
UIView *pausedState = [[UIView alloc] initWithFrame:self.frame];
IMSpeechControlButton *pasPlayButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameLeft forControl:self style:IMSpeechControlButtonStylePlay];
[pausedState addSubview:pasPlayButton];
[pasPlayButton release];
// Stop button
IMSpeechControlButton *pasStopButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameRight forControl:self style:IMSpeechControlButtonStyleStop];
[pausedState addSubview:pasStopButton];
[pasStopButton release];
[controls insertObject:pausedState atIndex:(NSUInteger)IMSpeechControlStatePaused];
[pausedState release];
// store the array in an instance variable
self.controlButtons = controls;
[controls release];
// Set the view to it's first state (stopped)
IMSpeechControlButton *stoppedState = (IMSpeechControlButton *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStateStopped];
[self.view addSubview:stoppedState];
controlState = IMSpeechControlStateStopped;
}
- (IMSpeechEngine *)speechEngine {
if (nil == speechEngine) {
self.speechEngine = [IMSpeechEngine sharedManager];
}
return speechEngine;
}
- (void)changeToState:(IMSpeechControlState)state {
// This line caused my problem
// IMSpeechControlButton *currentView = [[self.view subviews] objectAtIndex:0];
// It should look like this
UIView *currentView = [[self.view subviews] objectAtIndex:0];
switch (state) {
case IMSpeechControlStateStopped:
{
UIView *stoppedState = (UIView *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStateStopped];
[self.view addSubview:stoppedState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
stoppedState.alpha = 0.15;
}
completion:^(BOOL finished)
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{stoppedState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStateStopped;
break;
}
case IMSpeechControlStatePlaying:
{
UIView *playingState = [self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStatePlaying];
[self.view addSubview:playingState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
playingState.alpha = 0.15;
}
completion:^(BOOL finished){
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{playingState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStatePlaying;
break;
}
case IMSpeechControlStatePaused:
{
UIView *pausedState = (UIView *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStatePaused];
[self.view addSubview:pausedState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
pausedState.alpha = 0.15;
}
completion:^(BOOL finished){
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{pausedState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStatePaused;
break;
}
default:
NSLog(#"Error %lu is not a recognized IMSpeechControlState", state);
break;
}
}
- (void)speechEngineDidFinishSpeaking:(NSNotification *)notifictation {
// This notification is only sent if it has finished speaking and is therefore stopped.
[self changeToState:IMSpeechControlStateStopped];
}
- (void)play {
NSString *text = [delegate speechControlNeedsText:self];
[self.speechEngine speakText:text];
[self changeToState:IMSpeechControlStatePlaying];
}
- (void)pause {
[self.speechEngine pauseSpeaking];
[self changeToState:IMSpeechControlStatePaused];
}
- (void)stop {
[self.speechEngine stopSpeaking];
[self changeToState:IMSpeechControlStateStopped];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self forKeyPath:kDidFinishSpeakingNotificationName];
[super dealloc];
}
#end
// IMSpeechControlButton.h
#import "IMSpeechControl.h"
typedef enum {
IMSpeechControlButtonStylePlay,
IMSpeechControlButtonStylePause,
IMSpeechControlButtonStyleStop
}IMSpeechControlButtonStyle;
#interface IMSpeechControlButton: UIView {
IMSpeechControl *speechControl;
UIImageView *imageView;
}
#property (nonatomic, assign) IMSpeechControl *speechControl;
#property (nonatomic, retain) UIImageView *imageView;
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style;
#end
// IMSpeechControlButton.m
#import "IMSpeechControlButton.h"
#import <Foundation/NSObjCRuntime.h>
#implementation IMSpeechControlButton
#synthesize speechControl;
#synthesize imageView;
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style
{
NSString *str;
switch (style) {
case IMSpeechControlButtonStylePlay:
str = #"IMSpeechControlPlay";
break;
case IMSpeechControlButtonStylePause:
str = #"IMSpeechControlPause";
break;
case IMSpeechControlButtonStyleStop:
str = #"IMSpeechControlStop";
break;
default:
break;
}
isa = NSClassFromString(str);
// the speechControl must be set before calling subclass implementation of initWithFrame
self.speechControl = control;
return [self initWithFrame:aRect];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code.
}
*/
- (void)dealloc {
[super dealloc];
}
#end
All the control buttons have the exact same code as the play button, except that the handleGesture method calls the appropriate play/pause/stop function on the speechControl. The only reason I created all of them was so each of them could have their own images and so they can play different animations before changing state, but I didn't get to that yet.
// IMSpeechControlPlay.h
#import "IMSpeechControlButton.h"
#interface IMSpeechControlPlay : IMSpeechControlButton {
}
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;
#end
// IMSpeechControlPlay.m
#import "IMSpeechControlPlay.h"
#implementation IMSpeechControlPlay
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code.
// TODO: set the image view
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gestureRecognizer.numberOfTapsRequired = 1;
gestureRecognizer.numberOfTouchesRequired = 1;
gestureRecognizer.delaysTouchesBegan = YES;
[self addGestureRecognizer:gestureRecognizer];
[gestureRecognizer release];
}
return self;
}
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
[speechControl play];
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code.
}
*/
- (void)dealloc {
[super dealloc];
}
#end
I found the problem, it was in this line:
IMSpeechControlButton *currentView = (IMSpeechControlButton *)[[self.view subviews] objectAtIndex:0]; in the changeState method.
I'd changed and moved this line several times during development, and had an older version of the file where it was correctly stated as:
UIView *currentView = [[self.view subviews] objectAtIndex:0];
but I just noticed that the file I was building has the first version. The version that I copied the source from turned out to be an old version that looked like this:
UIView *currentView = (IMSpeechControl *)[[self.view subviews] objectAtIndex:0];
Changing that to a UIView pointer makes it work. Looking at the debug info it looks like I was wrong about the subviews not getting retained, they were actually just unavailable in the debugger being cast away when the play control was pressed. Setting the speechControl variable before calling init actually works fine.
Thanks for all the advice and so quickly.
This method is just really wrong
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style
Basically there is nothing to return here
self.speechControl = control;
return [self initWithFrame:aRect];
Because you haven't actually inited "self" yet. You need to rewrite that function to have a proper initializer like the one you have in IMSpeechControlPlay.