How to call a method in a sub UIView? - iphone

i have a uiviewcontroller named MainViewController,
i add a subview in it (name:DetailView) in viewdidload event.
when i press the a button in mainviewController i want to call a method in subview, writeSomething.
//DetailView is a subclass of a UIView.
in Mainviewcontroller:
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:#"DetailView" owner:self options:nil];
UIView *aView= [nibViews objectAtIndex:0];
aView.tag=100;
self.detailView=aView;
[detailContainer addSubview:self.detailView];
}
-(void) writeAnythingInSubViewsLabel{
//i tried to reach detail UIView
NSArray* subviews = [NSArray arrayWithArray: detailContainer.subviews];
for (UIView *xView in subviews) {
if ([xView isKindOfClass:[UIView class]] && xView.tag==100) {
NSLog(#"%#", xView);
[xView writeSomething:#"hello"];
break;
}
}
}
in DetailView:
-(void) writeSomething:(NSString *)anyText{
NSLog(#"%#", anyText);
}
but it gives error;
[UIView writeSomething]: unrecognized selector sent to instance....
it also gives a warning before build, UIView may not respond writeSomething...
i kknow UIView and DetailView doesnt match. thats why it gives warning.
i tried also for (DetailView *xView in subviews) {
but it never finds the tag in the loop.
i hope anyone help me...
EDIT:
DetailView.m
#import "DetailView.h"
#implementation DetailView
#synthesize image, button, label;
#synthesize delegate;
- (void)dealloc {
[image release];
[button release];
[label release];
[super dealloc];
}
- (id) initWithCoder: (NSCoder *) decoder {
return self;
}
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Initialization code
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
-(IBAction) backButtonPressed:(id) sender{
[delegate hideDetailView];
}
-(void) writeSomething:(NSString *)anyText{
NSLog(#"%#", anyText);
}
#end

It seems that you are creating a UIView rather than a DetailView.
As a result, it is not finding the method you created in DetailView, since it thinks that xView is actually a UIView - which doesn't have the writeSomething method.
You should go back to your header, import the DetailView.h file, and make sure that detailView is actually an object of type DetailView, rather than UIView.
In viewDidLoad, instead of
NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:#"DetailView" owner:self options:nil];
UIView *aView= [nibViews objectAtIndex:0];
aView.tag=100;
self.detailView=aView;
try using:
self.detailView = [[DetailView alloc] initWithNibName:#"DetailView" bundle:[NSBundle mainBundle]];
Then, instead of the loop and such in the .m file, you can simply use:
[self.detailView writeSomething:#"words"];
Good luck!

Try [detailContainer viewWithTag:100]

Not sure what the problem is, but why not save the created view in a member?
This way it will be accessible to you without having to re-search for it.

Related

Remove Custom View from superView

I have My own UIView :
#import <UIKit/UIKit.h>
#interface MultipleSlotsClientView : UIView
-(IBAction)didPressCloseBtn:(id)sender;
#end
And this is the implementation:
#implementation MultipleSlotsClientView
- (id)initWithFrame:(CGRect)frame {
self = [[[[NSBundle mainBundle] loadNibNamed:#"MultipleSlotsClientView" owner:self options:nil] objectAtIndex:0] retain];
if (self) {
self.frame = frame;
}
return self;
}
#pragma mark
#pragma mark IBAction
-(IBAction)didPressCloseBtn:(id)sender {
[self removeFromSuperview];
}
#end
And i have a btn that connect to the didPressCloseBtn method and when i press the button the method called but the View won't remove from the superview.
This is how i alloc the UIView and add it:
MultipleSlotsClientView *multiView = [[[MultipleSlotsClientView alloc] initWithFrame:self.view.frame] autorelease];
[self.view addSubview:multiView];
Any idea why the view won't disappear?
Just try to connect like below screenshot, don't connect to FileOwner.
Step-1 write the below method in NSObject Category class
+ (id)loadNibNamed:(NSString *)NibName {
NSObject *cl = nil;
if (NSClassFromString(NibName) != nil) {
NSArray *arr = [[NSBundle mainBundle] loadNibNamed:NibName owner:self options:nil];
for (id cls in arr) {
if([cls isKindOfClass:NSClassFromString(NibName)])
{
cl = cls;
break;
}
}
}
return cl;
}
Step:2 call the respective Class like
MultipleSlotsClientView *multiView = [MultipleSlotsClientView loadNibNamed:#"MultipleSlotsClientView"]
[self.view addSubview:multiView];
And No need to write anything in "initWithFrame".
Try this. It may work for you.
This is an answer to a comment. Just because you cannot format comments nicely. It is about why you leak memory in your code and how you could write the code to overcome the issue.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Do something here if you have to.
// Currently there is no reason for overwriting intiWithFrame: at all.
}
return self;
}
And instead of:
MultipleSlotsClientView *multiView = [[[MultipleSlotsClientView alloc] initWithFrame:self.view.frame] autorelease];
Just do:
MultipleSlotsClientView *multiView= [[[[NSBundle mainBundle] loadNibNamed:#"MultipleSlotsClientView" owner:self options:nil] objectAtIndex:0] autorelease];
multiView.frame = self.view.frame;
Well, whether you should retain or autorelease it or nothing of that kind at all depends on the other code. Assuming that you add multiView to the subview hierarchy, which will retain it, autorelease is fine.

Why does this init method when loading a nib return an object out of scope?

Why does this init method return an object out of scope?
Using XCode 4.2, base SDK of 4.3, and ARC, I'm trying to load an UIView from a nib (not a UIViewController). I need to not use a UIViewController at all in the process.
After reading this answer to an S.O. question here, it looks like it can be done:
How to load a UIView using a nib file created with Interface Builder
(The answer by user "MusiGenesis" describes the process)
I created a sub-class of UIView with a single label:
#interface MyView : UIView
#property (unsafe_unretained, nonatomic) IBOutlet UILabel *textLabel;
#end
In the implementation I override initWithFrame:
- (id)initWithFrame:(CGRect)frame
{
//self = [super initWithFrame:frame];
self = [JVUIKitUtils initWithNibName:#"MyView" withOwner:self];
if( self )
{
NSLog(#"Created");
}
return self;
}
In I.B. I created a file named "MyView.xib" with a single view. It has a label as a sub-view, and I created the label property by dragging it to the h file.
And in another file, I created this re-usable static method:
+ (id)initWithNibName:(NSString*)nibName withOwner:(id)uiView
{
id object = nil;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName owner:uiView options:nil]; // 1 object, out of scope
for( id tempObject in bundle)
{
if( [tempObject isKindOfClass:[uiView class]] ) object = tempObject;
break;
}
return object;
}
As you can see in the following screen shot, the bundle has one object reference, but it's out of scope.
And debugging:
This is my code for instantiation:
subView = [[MyView alloc] initWithFrame:CGRectZero]; // ok
NSAssert(subView != nil, #"MyView was nil"); // fail
Any ideas on why the other S.O. poster was able to get it to work but this does not?
The use of the owner seems a bit confusing in the way that you are loading a nib. It appears that you are trying to use the view as both the owner of the nib and the first object in it.
Are you trying to load MyView from your nib (i.e. is the class of the view inside your nib files defined as MyView) or are you trying to load a subview of MyView from the nib?
If the view inside your nib is a MyView, here's how to load it. Create this static method as a category on UIView:
#implementation UIView (NibLoading)
+ (id)viewWithNibName:(NSString*)nibName
{
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName owner:nil options:nil];
if ([bundle count])
{
UIView *view = [bundle objectAtIndex:0];
if ([view isKindOfClass:self])
{
return view;
}
NSLog(#"The object in the nib %# is a %#, not a %#", nibName, [view class], self);
}
return nil;
}
#end
That will let you load any kind of view from a nib file (the view needs to be the first item defined in the nib). You would create your view like this:
MyView *view = [MyView viewWithNibName:#"MyView"];
If the view inside the nib is not a MyView, but you want to load it as a subview of MyView, with MyView defined as the file's owner in the nib file, do it like this:
#implementation UIView (NibLoading)
- (void)loadContentsFromNibName:(NSString*)nibName
{
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil];
if ([bundle count])
{
UIView *view = [bundle objectAtIndex:0];
if ([view isKindOfClass:[UIView class]])
{
//resize view to fit
view.frame = self.bounds;
//add as subview
[self addSubview:view];
}
NSLog(#"The object in the nib %# is a %#, not a UIView", nibName, [view class]);
}
}
#end
Using that approach, just create your view as normal using initWithFrame, then call loadContentsFromNibName to loa the contents from a nib. You would load your view like this:
MyView *view = [[MyView alloc] initWithFrame:CGRect(0,0,100,100)];
[view loadContentsFromNibName:#"MyView"];
Your [JVUIKitUtils initWithNibName:#"MyView" withOwner:self] approach is all wrong. When an init method is called, the object is already allocated by the call to [MyView alloc]. The reason why you chain the calls to the super init method is so that this will daisy chain all the way down to the NSObject init method, which simply returns the instance it is.
By setting "self" to the (autoreleased) instance value returned by your JVUIKitUtils, you are effectively setting it to a memory address other than what was allocated by the call to [MyView alloc], which generates the out of scope error.
Instead, don't create the init method your are trying to create. You already have the method to create and initialize your nib-based view in your state method. I would change the name and do something like:
+ (UIView)newViewWithNibName:(NSString*)nibName withClassType:(Class)uiView
{
id object = nil;
// you need some object to assign nib file owner to. use an NSObject.
NSObject* owner = [[NSObject alloc] init];
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName owner:owner options:nil]; // 1 object, out of scope
for( id tempObject in bundle)
{
if( [tempObject isKindOfClass:] ) object = [tempObject retain];
break;
}
[owner release];
return object;
}
and then call it like:
MyView* subView = [JVUIKitUtils viewWithNibName:#"MyView" withClassType:[MyView class]];
I haven't tested this code. Your milage might vary.

question about init and load from xib for a custom UIView

I am trying to create a custom UIView class which loads from a xim file that contains the interface for that view. I am trying to encapsulate the [NSBundle mainBundle] loadNibNamed...] within the init method of my custom view as follows:
- (id)init
{
self = [super init];
if (self)
{
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:#"LoadingV" owner:self options:nil];
self = [(LoadingV*)[nibViews objectAtIndex: 0] retain];
}
return self;
}
I want to know:
Is this an acceptable way of doing so? And is there any better way?
Should i keep the "retain" given that i do not call [self release] in dealloc?
Cheers
AF
No, this is not acceptable, it's bad practice and you actually have a memory leak there.
Better way to do this is to use a pattern called "factory".
Example:
#interface CustomView: UIView
#end
#implementation CustomView
- (void)awakeFromNib {
// custom view loaded from nib
}
#end
#interface UIView (Nib)
+ (UIView *)viewFromNib:(NSString *)nibName bundle:(NSBundle *)bundle;
#end
#implementation UIView (Nib)
+ (UIView *)viewFromNib:(NSString *)nibName bundle:(NSBundle *)bundle {
if (!nibName || [nibName length] == 0) {
return nil;
}
UIView *view = nil;
if (!bundle) {
bundle = [NSBundle mainBundle];
}
// I assume, that there is only one root view in interface file
NSArray *loadedObjects = [bundle loadNibNamed:nibName owner:nil options:nil];
view = [loadedObjects lastObject];
return view;
}
#end
Usage:
// CustomView.xib contains one View object with its class set to "CustomView"
CustomView *myView = (CustomView *)[UIView viewFromNib:#"CustomView" bundle:nil];

Objective-C Memory management: ViewController in a ViewController that's inside a UITabBarController

I have a UITabBarController with three ViewControllers (A, B, and C). In ViewControllerB I have a UIScrollView. The UIScrollView consists of several instances of my PhotoViewController. These PhotoViewController objects are called from ViewControllerA, not ViewController B, where they're located.
The PhotoViewController instances have a UIImage and a two buttons. And at first, when I clicked on a button in one of my PhotoViewController instances I received a 'Thread 1: Program received signal: "EXC_BAD_ACCESS"' error. Looking around on stackoverflow that error seems to appear whenever there are memory management issues.
Since I was creating the PhotoViewController objects in a loop from a method called in ViewControllerA, and releasing those objects, I figured that by the time I switched over to ViewControllerB they were already released - and hence the memory issue.
But that's just my guess. Could you tell me if I should just stop releasing the PhotoViewController objects inside of the loop code? Because that's what I did (just commented that line out) and the program "works" fine. However, I'm still not sure if this is the proper way to handle it and if it is causing unknown memory management issues.
Here is some of my code:
ViewControllerA.m
//Creating an album in ViewControllerB, the photos in the album are PhotoViewController objects
-(IBAction)showAlbum:(UIButton *)sender
{
//Go goes here to get an album and display it in the UIScrollView
albumID = #"ALBUM_ID";
NSString* graphUrl = [NSString stringWithFormat:#"%#/photos?limit=10", albumID];
[_facebook requestWithGraphPath:graphUrl andDelegate:self];
}
...
- (void)request:(FBRequest *)request didLoad:(id)result {
//Code for array of photos
NSLog(#"%#",result);
NSString *requestType = [request.url stringByReplacingOccurrencesOfString:#"https://graph.facebook.com/" withString:#""];
if ([requestType isEqualToString:[NSString stringWithFormat:#"%#/photos?limit=10", albumID]]){
NSArray *photoAlbumArray=(NSArray*)[result valueForKey:#"data"];
[self.label setText:[NSString stringWithFormat:#"%i", [photoAlbumArray count]]];
for(UIViewController *controller in self.tabBarController.viewControllers)
{
if([controller isKindOfClass:[ViewControllerB class]])
{
ViewControllerB *mtbvc = (ViewControllerB *)controller;
[mtbvc setArray:photoAlbumArray];
self.tabBarController.selectedIndex = 1;//switch over to the second view to see if it worked
}
}
}
...
#end
ViewControllerB.m
//loop where I create PhotoViewController objects
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
arrayCount = [array count];
scroller.delegate=self;
scroller.pagingEnabled=YES;
scroller.directionalLockEnabled=YES;
scroller.showsHorizontalScrollIndicator=NO;
scroller.showsVerticalScrollIndicator=NO;
//should have an array of photo objects and the number of objects, correct?
scrollWidth = 0;
scroller.contentSize=CGSizeMake(arrayCount*scroller.frame.size.width, scroller.frame.size.height);
for (int i = 0; i < arrayCount;i++) {
PhotoViewController *pvc = [[PhotoViewController alloc] initWithNibName:#"PhotoViewController" bundle:nil];
UIImageView *scrollImageView = [[UIImageView alloc] initWithFrame:CGRectOffset(scroller.bounds, scrollWidth, 0)];
CGRect rect = scrollImageView.frame;
pvc.view.frame = rect;
[pvc view];
pvc.label.textColor = [UIColor whiteColor];
id individualPhoto = [array objectAtIndex:i];
NSLog(#"%#",individualPhoto);
NSArray *keys=[individualPhoto allKeys];
NSLog(#"%#",keys);
NSString *imageURL=[individualPhoto objectForKey:#"source"];
//here you can use this imageURL to get image-data and display it in imageView
NSURL *url = [NSURL URLWithString:imageURL];
NSData *data = [NSData dataWithContentsOfURL:url];
pvc.imageView.image = [[UIImage alloc] initWithData:data];
pvc.label.text = [individualPhoto objectForKey:#"id"];
//check to make sure the proper URL was passed
//I have an imageView next to the UIScrollView to test whether that works - it does.
[scroller addSubview:pvc.view];
[scrollImageView release];
//[pvc release];
scrollWidth += scroller.frame.size.width;
}
if (arrayCount > 3) {
pageControl.numberOfPages=3;
} else {
pageControl.numberOfPages=arrayCount;
}
pageControl.currentPage=0;
//[self.view addSubview:scroller];
}
PhotoViewController.m
#import "PhotoViewController.h"
#implementation PhotoViewController
#synthesize label, imageView;
-(IBAction)likeButton:(UIButton *)sender
{
//code goes here
for(UIViewController *controller in self.tabBarController.viewControllers)
{
if([controller isKindOfClass:[DemoAppViewController class]])
{
DemoAppViewController *davc = (DemoAppViewController *)controller;
[davc likePicture:self.label.text];
}
}
self.tabBarController.selectedIndex = 0;//switch over to the third view to see if it worked
}
-(IBAction)skipButton:(UIButton *)sender
{
//code goes here
}
-(IBAction)likeCommentButton:(UIButton *)sender
{
//code goes here
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (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.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
PhotoViewController.h
#import <UIKit/UIKit.h>
#import "DemoAppViewController.h"
#import "MyTabBarViewController.h"
#interface PhotoViewController : UIViewController {
IBOutlet UILabel *label;
IBOutlet UIImageView *imageView;
UIButton *likeButton;
UIButton *skipButton;
UIButton *likeCommentButton;
}
#property (nonatomic, retain) UILabel *label;
#property (nonatomic, retain) UIImageView *imageView;
-(IBAction)likeButton:(UIButton *)sender;
-(IBAction)skipButton:(UIButton *)sender;
-(IBAction)likeCommentButton:(UIButton *)sender;
#end
To write iOS apps, it is critical that you understand the memory management rules.
In ViewControllerB, viewDidLoad, you alloc the pvc.
Further down, you add the pvc's view as a subview of the scroller. This retains the pvc's view, but not the pvc itself. Then when you release the pvc, it's retain count is zero, and when you reference it later, it's gone. Crash. It seems like you need to pass in and retain a reference to the pvc in the controller that's using it.
I am not sure why you are using PhotoViewController(Subclassing UIViewController) instead of PhotoView(subclassing UIView). As you are not using any of the facility of the viewcontroller(no life cycle method and other).
If you subclass PhotoViewController with UIView and remove the viewcontroller's methods it will work and will not cause any memory issues as you have aleready discussed in your discussion with Rayfleck. ( it will be retained by parent view controller.)
If you are thinking about the events, then these will also be handled by view itself. But if you want to handle it in your controller then you can easily delegate, or pass your controller reference and invoke the event on it.
Thanks,

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.