InitWithFrame not executed if view is a property - iphone

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...].

Related

How to init a UIView

In my main viewController, I have this:
// .h
#import "YNSlidingMenu.h"
#interface YNAddNoteViewController : UIViewController
#property (nonatomic, strong) YNSlidingMenu *slidingMenu;
#end
// .m
- (void)setUpSlidingMenu{
_slidingMenu = [[YNSlidingMenu alloc]init];
[self.view addSubview:_slidingMenu];
}
In YNSlidingMenu.m, I have this:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self configureView];
}
return self;
}
- (id)init{
self = [super init];
if (self) {
NSLog(#"init");
}
return self;
}
- (void)configureView{
NSLog(#"configureView");
self.rect = CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, 40);
}
YNSlidingMenu will get its frame for itself based on its contents, so I don't want to init it on my viewController with initWithFrame.
What do I need to do? NSLog(#"init"); doesn't get called. How can I initialize my custom view without setting the frame from my viewController?
Move [self configureView] out of your initWithFrame: method and into your init method as your calling init when you initialize your view. At least that's what your sample code looks like.
I don't think it can get its frame base on its content, unless of cause you're using auto layout.
A proper approach is that you init you view with CGRectZero. Then set the frame latter when you are ready.
Or you can adopting auto layout.

Adding a UILabel Programmatically with the Current Time

I have an issue. Maybe I've got something in the wrong place or something, but dang I just can't figure it out! Any help would be appreciated.
I am trying to simply make a clock programmatically that shows only in my subview.
I've setup a timer along with an updater -(void) that I want to show in my subView that I create. I am building the subview programmatically which is the reason for not adding an IBOutlet and just connecting it in my storyboard - I'm trying do do it all with just code.
I am getting the error on my updateTimer label that says - Use of undeclared identifier 'rightLabel' and it's just not working out for me. HA HA - any help would be GREATLY appreciated!
.h
#interface DemoRootViewController : UIViewController <PaperFoldViewDelegate> {
NSTimer *timer;
}
#property (nonatomic, strong) UIView *rightView;
-(void)updateTimer;
.m
- (id)init
{
self = [super init];
if (self) {
//Timer Setup
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(updateTimer) userInfo:nil repeats:YES];
//PaperFold Setup
_paperFoldView = [[PaperFoldView alloc] initWithFrame:CGRectMake(0,0,[self.view bounds].size.width,[self.view bounds].size.height)];
[_paperFoldView setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth];
[self.view addSubview:_paperFoldView];
//Setup Subview
_rightView = [[UIView alloc] initWithFrame:CGRectMake(0,0,240,[self.view bounds].size.height)];
//Setup UILabel
UILabel *rightLabel = [[UILabel alloc] initWithFrame:_rightView.frame];
[_rightView addSubview:rightLabel];
//Using PaperFold Framework to Add the Subview (this works fine)
[_paperFoldView setRightFoldContentView:_rightView foldCount:2 pullFactor:1];
}
return self;
}
-(void)updateTimer {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"hh:mm:ss"];
//This is where I get my error "Use of Undeclared Identifier 'rightLabel'
rightLabel.text = [formatter stringFromDate:[NSDate date]];
}
#end
You don't have rightLabel declared as a property (or instance variable). Just change your .m to the code below:
.m
#interface DemoRootViewController ()
#property (nonatomic, strong) UILabel *rightLabel;
#property (nonatomic, strong) NSDateFormatter *dateFormatter;
#end
#implementation DemoRootViewController
- (id)init
{
self = [super init];
if (self) {
//Timer Setup
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(updateTimer) userInfo:nil repeats:YES];
//PaperFold Setup
_paperFoldView = [[PaperFoldView alloc] initWithFrame:CGRectMake(0,0,[self.view bounds].size.width,[self.view bounds].size.height)];
[_paperFoldView setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth];
[self.view addSubview:_paperFoldView];
//Setup Subview
_rightView = [[UIView alloc] initWithFrame:CGRectMake(0,0,240,[self.view bounds].size.height)];
//Setup UILabel
self.rightLabel = [[UILabel alloc] initWithFrame:_rightView.frame];
[_rightView addSubview:self.rightLabel];
//Using PaperFold Framework to Add the Subview (this works fine)
[_paperFoldView setRightFoldContentView:_rightView foldCount:2 pullFactor:1];
//Formatter setup
self.formatter = [[NSDateFormatter alloc] init];
[self.formatter setDateFormat:#"hh:mm:ss"];
}
return self;
}
-(void)updateTimer {
self.rightLabel.text = [self.formatter stringFromDate:[NSDate date]];
}
#end
The #interface DemoRootViewController () part is called a class extension. It essentially allows you "private" properties, so they are not exposed through the header file. If you want them exposed, simply put the two property definitions into the .h file.
Since you want to update the label, make the label variable an instance variable. Then you can create it in the init method and access it in the updateTimer method.
Also, make the date formatter an instance variable so you only need to create it once in the init method. No need to create a new date formatter every half second.
And since your timer should be a private instance variable, remove it from the .h file and put in the .m file.
#implementation DemoRootViewController {
NSTimer *timer;
}
Add the rightLabel and formatter ivars there too.
Also, remove the declaration of updateTimer from the .h file. This is a private method only used by the implementation in the .m file. Adding it to the .h file allows other classes to call the method.
You have to declare UILabel *rightLabel in .h file because you are using rightLabel in another method also.

Why is my delegate method not called?

this is probably simple but I'm stuck!
Basically I have a parent and child view controller, and I'm trying to pass data from the child to the parent.
//Child VC interface
#protocol ANSearchGetawayFilterDelegate
-(void)selectedCell:(NSString *)cellTitle;
#end
#interface ANSearchGetawayFilterViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate>
{
NSString* cellTitle;
}
#property (nonatomic, assign) id<ANSearchGetawayFilterDelegate> delegate;
#end
//Child VC implementation
#implementation ANSearchGetawayFilterViewController
#synthesize delegate = _delegate;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
cellTitle = selectedCell.textLabel.text;
[[self delegate] selectedCell:cellTitle];
[self dismissModalViewControllerAnimated:YES];
}
//Parent VC interface
#import "ANSearchGetawayFilterViewController.h"
#interface ANGetawayFilterViewController : UIViewController <ANSearchGetawayFilterDelegate>
{
NSString* _cellText;
}
//Parent VC Implementation
- (id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
// Custom initialization
ANSearchGetawayFilterViewController *search = [[ANSearchGetawayFilterViewController alloc] init];
search.delegate = self;
}
return self;
}
//delegate method
-(void)selectedCell:(NSString *)cellTitle
{
_cellText = cellTitle;
NSLog(#"cell text %#", _cellText);
}
the delegate method is never called and when is NSLog the _cellText else where it comes up as null...what am I doing wrong? Thanks!
You are most likely creating a new instance of ANSearchGetawayFilterViewController when you present it and not configuring the delegate on it.
When you called
ANSearchGetawayFilterViewController *search = [[ANSearchGetawayFilterViewController alloc] init];
search.delegate = self;
you created an instance of ANSearchGetawayFilterViewController and then set the delegate up correctly, but you never stored this instance of ANSearchGetawayFilterViewController anywhere. So later on when you come to present it you call again
ANSearchGetawayFilterViewController *search = [[ANSearchGetawayFilterViewController alloc] init];
which gives you a completely different instance, which you then need to configure again. For example
ANSearchGetawayFilterViewController *search = [[ANSearchGetawayFilterViewController alloc] init];
ANSearchGetawayFilterViewController *search1 = [[ANSearchGetawayFilterViewController alloc] init];
NSLog(#"%d", search1 == search);
#=> 0
To fix update your code to be
- (BOOL)textFieldShouldBeginEditing:(UITextField*)textField;
{
BOOL shouldBeginEditing = YES;
NSLog(#"text field should begin editing");
ANSearchGetawayFilterViewController *myANSearchGetawayFilterViewController = [[[ANSearchGetawayFilterViewController alloc] init] autorelease];
myANSearchGetawayFilterViewController.delegate = self; // <--- configure the delegate
[self presentModalViewController:myANSearchGetawayFilterViewController animated:YES];
[self closeAllPickers];
return shouldBeginEditing;
}
I wouldn't make it an ivar as the likelihood is you will present this viewController momentarily just to select some data and then get rid of it, so it is probably safe to discard it and make a new one each time.
Au contraire, the delegate method is being called (hence the NSLog()). However, _cellText is (null) because the value being passed in is nil, ergo selectedCell.textLabel.text.
Firstly, are you sure that the -selectedCell method is being called?
You can do this by putting an NSLog() before or after -tableViewDidSelectRow...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
...
NSLog(#"TABLEVIEW DID SELECT ROW BEFORE -> %# <-", cellTitle);
[[self delegate] selectedCell:cellTitle];
NSLog(#"TABLEVIEW DID SELECT ROW DELEGATE CALLED");
...
}
Also, you might want to do some cleanup (optional)
Firstly, you are leaking in your initialisation method. Either set the ANGetawayFilterViewController as a property of the parent class using the delegate, or release it after you set the delegate.
Secondly, in the -tableViewDidSelectRow, your code assumes that the delegate has the -selectedCell method coded. If you don't have the method implemented, then the application will result in a crash. You can prevent this by checking to see if the delegate -respondsToSelector...:
if ([self.delegate respondsToSelector:#selector(selectedCell:)]) {
[self.delegate selectedCell:cellTitle];
}
Thirdly, the method of which is being called by the delegate to notify the parentViewController doesn't follow the general schema that delegate methods use, with the exception of -numberOfRowsInSection (UITableViewDelegate). Your method should contain the actual ANFilterGetawayViewController instance too:
- (void) filterGetawayViewController:(ANSearchGetawayFilterViewController *) controller didSelectCellWithTitle:(NSString *) title {
...
}
It can be called as such:
[self.delegate filterGetawayViewController:self didSelectCellWithTitle:cellTitle];
Are you using ARC? Because when the init function ends, your object (and it's reference to the delegate) are cleaned up. What happens if you make the search variable a global one (defining it in your header and initializing it in your code)?
Assuming you are using ARC:
You need to make a retained #property for your ANSearchGetawayFilterViewController instance. It will have been released by ARC by the time the delegate method is called. Do something like this.
#property (strong, nonatomic) ANSearchGetawayFilterViewController *search;
...
#synthesize search = _search;
- (id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
// Custom initialization
self.search = [[ANSearchGetawayFilterViewController alloc] init];
self.search.delegate = self;
}
return self;
}
Not related to your problem, but best practice is to check if the delegate actually implements the method you expect it to before calling it, like so:
if ([self.delegate respondsToSelector:#selector(selectedCell:)]) {
[self.delegate selectedCell:cellTitle];
}

Adding a NSTimer to a button

I have searched all over the web with no luck of making this project work with a timer, I end up with a crashed app every time when using the timer.
For testing and learning purposes I build little simple apps. It is a button that sends a rocket from the bottom of the screen and disappears off screen, along with a rocket sound effect.
I am wanting to add a Timer to the button so that when the button is held down the rocket will launch, reset, and launch over and over until I release the button. I think my only hope now is to paste code from my .h & .m files and hopefully someone can tell me what I need to do and where the proper code needs to be added for this project.
Thank you so much for the help, it is greatly appreciated.
.H FILE:
// MVViewController.h
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface MVViewController : UIViewController
#property (strong, nonatomic) IBOutlet UIImageView *moveMe2;
//New action to repeat launch (Touch Down)
- (IBAction)rocketRepeat:(id)sender;
//New action to stop launch (Touch Up Inside)
- (IBAction)rocketStop:(id)sender;
//This is the original launch button (Touch Down)
- (IBAction)yourRocketButton:(id)sender;
#end
.M FILE
// MVViewController.m
#import "MVViewController.h"
#interface MVViewController ()
#end
#implementation MVViewController {
AVAudioPlayer *audioPlayer;
}
#synthesize moveMe2;
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[self setMoveMe2:nil];
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return ((interfaceOrientation == UIInterfaceOrientationPortrait) || (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown));
}
- (IBAction)rocketRepeat:(id)sender
//Getting error "Use of undeclared identifier 'yourRocketButton'
{
[yourRocketButton addTarget:self action:#selector(rocketRepeat:) forControlEvents:UIControlEventTouchDown];
}
- (IBAction)rocketStop:(id)sender
//Getting error "Use of undeclared identifier 'yourRocketButton'
{
[yourRocketButton addTarget:self action:#selector(rocketStop:) forControlEvents:UIControlEventTouchUpInside];
}
- (IBAction)yourRocketButton:(id)sender {
moveMe2.center = CGPointMake(100.0f, 408.0f);
[UIView animateWithDuration:2.0 animations:^{moveMe2.center = CGPointMake(100, -55);}];
}
#end
########
EDIT *This is what finally worked*
// RKViewController.m
#import "RKViewController.h"
#interface RKViewController ()
#end
#implementation RKViewController
#synthesize RocketMove;
#synthesize Launch;
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[self setRocketMove:nil];
[self setLaunch:nil];
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return ((interfaceOrientation == UIInterfaceOrientationPortrait) || (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown));
}
- (IBAction)rocketRepeat:(id)sender {
[self performSelector:#selector(rocketRepeat:) withObject:self afterDelay:1.0];
RocketMove.center = CGPointMake(100.0f, 408.0f);
[UIView animateWithDuration:1.0 animations:^{RocketMove.center = CGPointMake(100, -55);}];
}
- (IBAction)rocketStop:(id)sender {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
#end
// RKViewController.h
#import <UIKit/UIKit.h>
#interface RKViewController : UIViewController
#property (strong, nonatomic) IBOutlet UIImageView *RocketMove;
#property (strong, nonatomic) IBOutlet UIButton *Launch;
- (IBAction)rocketRepeat:(id)sender;
- (IBAction)rocketStop:(id)sender;
#end
You have to use UIControlEvent for this purpose.
1. You need two separate IBActions for each purpose, say one for holding down the button, one after the button is released.
2. For holding down the button you need to use UIControlEventTouchDown. So have a rocketRepeat action where you keep calling the rocket action using the NSTimer with regular intervals and use:
[yourRocketButton addTarget:self action:#selector(rocketRepeat:) forControlEvents:UIControlEventTouchDown];
3. Then use another action with UIControlEventTouchUpInside where you will invalidate the NSTimer so the rocket stops. Call that action rocketStop or something and use:
[yourRocketButton addTarget:self action:#selector(rocketStop:) forControlEvents:UIControlEventTouchUpInside];
---EDIT---
Action 1 :
- (IBAction)rocketRepeat:(id)sender
{
//code for starting rocker, timer action
}
Action 2:
- (IBAction)rocketStop:(id)sender
{
//Code for stopping rocket
}
yourButton is not an action, its a UIButton. I hope you have created a button in the IB, drag and dropped the button. And in viewDidLoad you write these 2 lines of code :
Instead of yourButton you write the name of the button you have dragged dropped from the IB. I hope you know how to add a button from the interface builder and connect it.
- (void)viewDidLoad
{
[yourRocketButton addTarget:self action:#selector(rocketRepeat:) forControlEvents:UIControlEventTouchDown]; //For touch down button action
[yourRocketButton addTarget:self action:#selector(rocketStop:) forControlEvents:UIControlEventTouchUpInside]; //When button is let go.
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
If you want that the rockets are autolaunched once you press the button, you should add the following code to your rocketLaunch: method. If you want them to start appearing from the beginning, call this from your viewDidLoad method.
- (void)launchRocketsTimer
{
[self.timer invalidate]; //you have to create a NSTimer property to your view controller
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.5 target:self selector:#selector(scheduledRocketLaunch:) userInfo:nil repeats:YES];
}
- (void)scheduledRocketLaunch:(NSTimer *)t
{
moveme2.center = _bottom_point_; //set it where you would like it to start
[UIView animateWithDuration:2.0 animations:^{moveMe2.center = CGPointMake(100, -55);}];
}
Remember to release your timer in dealloc.
Oh, and one more thing:
You have a memory leak on your rocketsound: method when you allocate your AVAudioPlayer. You may replace the code with this one:
- (IBAction)rocketsound:(id)sender
{
NSURL *url = [NSURL fileURLWithPath: [NSString stringWithFormat:#"%#/rocketlaunch.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
if (self.audioPlayer == nil)
{
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error] autorelease]; //Note the use of setter property and the autorelease. (you could use your own code because the if will prevent the leak in this case).
}
audioPlayer.numberOfLoops = 0;
if (audioPlayer == nil)
NSLog(#"%#", [error description]);
else
[audioPlayer play];
}

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.