Ivar not being updated by tableViewSelectionDidChange


#1

I am having a problem where setVoice is not properly updating inside tableViewSelectionDidChange. I feel like I must be missing a “big concept” since inside the tableViewSelectionDidChange, if I have the NSSpeechSynthesizer say something, it uses the updated voice properly; but then when speakIt gets called, it always uses the default voice.

Can someone please point out where I’ve gone wrong? (Apologies in advance for my deviations from the textbook):

[code]//
// TTAppDelegate.h
// SpeakText
//
// Created by Jonathan DePrizio on 8/22/12.
// Copyright © 2012 Jonathan DePrizio. All rights reserved.
//

#import <Cocoa/Cocoa.h>

@interface TTAppDelegate : NSObject <NSApplicationDelegate, NSSpeechSynthesizerDelegate, NSTableViewDataSource, NSTableViewDelegate>
{
NSSpeechSynthesizer *_theVoice;
}

  • (IBAction)sayIt:(id)sender;
  • (IBAction)stopIt:(id)sender;
  • (IBAction)countCharacters:(id)sender;

@property (assign) IBOutlet NSWindow *window;
@property (weak) IBOutlet NSTextField *textField;
@property (weak) IBOutlet NSTextField *counterField;
@property (weak) IBOutlet NSButton *countButton;
@property (weak) IBOutlet NSButton *speakButton;
@property (weak) IBOutlet NSButton *stopButton;
@property (weak) IBOutlet NSTableView *_voiceTable;

@end
[/code]

[code]//
// TTAppDelegate.m
// SpeakText
//
// Created by Jonathan DePrizio on 8/22/12.
// Copyright © 2012 Jonathan DePrizio. All rights reserved.
//

#import “TTAppDelegate.h”

@implementation TTAppDelegate
@synthesize textField;
@synthesize counterField;
@synthesize countButton;
@synthesize speakButton;
@synthesize stopButton;
@synthesize _voiceTable;

  • (void)applicationDidFinishLaunching:(NSNotification *)aNotification
    {
    // Insert code here to initialize your application
    }

  • (void)controlTextDidChange:(NSNotification *)textChangeNotification
    {
    NSLog(@“text updated”);

    // this does not work, I don’t know why:
    [counterField setStringValue:@“I saw some stuff”];
    }

  • (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)finishedSpeaking
    {
    NSLog(@“I’m done talking now.”);
    [speakButton setEnabled:YES];
    [stopButton setEnabled:NO];
    }

  • (IBAction)countCharacters:(id)sender
    {
    NSLog(@“Char count: %lu”, [[textField stringValue] length]);
    [counterField setStringValue:@“Have text”];
    }

  • (IBAction)sayIt:(id)sender
    {
    NSString *textData = [textField stringValue];
    if ([textData length] == 0)
    {
    // This condition actually causes the thing to hang,
    // because didFinishSpeaking doesn’t get called if
    // nothing was spoken. So, simply return.
    return;
    }
    BOOL spoke = [_theVoice startSpeakingString];
    [speakButton setEnabled:NO];
    [stopButton setEnabled:YES];
    NSLog(@“Did I speak? I think %d in voice %@”, spoke, [_theVoice voice]);

}

  • (IBAction)stopIt:(id)sender
    {
    [_theVoice stopSpeaking];
    }

  • (id)init
    {
    self = [super init];
    if (self)
    {

    NSLog(@“init”);
    _theVoice = [[NSSpeechSynthesizer alloc] initWithVoice:nil];

    [_theVoice setDelegate:self];
    NSArray *voiceList = [NSSpeechSynthesizer availableVoices];
    NSString *voiceName;

    for (voiceName in voiceList)
    {
    NSLog(@"%@", [[voiceName componentsSeparatedByString:@"."] lastObject]);
    }
    NSLog(@“There are %lu voices available”, [[NSSpeechSynthesizer availableVoices] count]);
    // [textField setEditable:NO];
    }
    return self;

}

  • (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
    {
    NSInteger voiceCount = [[NSSpeechSynthesizer availableVoices] count];
    return voiceCount;
    }

  • (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
    {
    NSString *thisVoice = [[NSSpeechSynthesizer availableVoices] objectAtIndex:row];
    NSDictionary *thisVoiceAttributes = [NSSpeechSynthesizer attributesForVoice:thisVoice];
    return [thisVoiceAttributes objectForKey:NSVoiceName];
    }

-(void)tableViewSelectionDidChange:(NSNotification *)notification
{
assert(_voiceTable);
assert(_theVoice);
NSInteger currentRow = [_voiceTable selectedRow];
NSString *newVoice = [[NSSpeechSynthesizer availableVoices] objectAtIndex:currentRow];
[_theVoice setVoice];
NSLog(@“New voice: %@”, [_theVoice voice]);
// [_theVoice startSpeakingString:@“Test 123”];
}

@end[/code]


#2

What doesn’t work? If you mean you don’t see the text in the counterField, make sure that it is connected to the text field object itself in the NIB file.

Have you tried setting the speech voice inside the sayIt method?


#3

Hi ibex10, thanks for responding! I had forgotten about the counterField issue. What I was trying to do, was have counterField update on-the-fly, by using controlTextDidChange be called when a character is typed. NSLog is called, but counterField is not updated. I don’t think I understand how to create a connection between counterField and textField, if that is what you are saying. It seems like controlTextDidChange is unaware of the existence of counterField?

As for the main issue, if I change the voice inside (IBAction)sayIt:(id)sender, it does not work either. Changing the method to this causes it always to speak in Agnes, which is voice 0.

- (IBAction)sayIt:(id)sender { NSString *textData = [textField stringValue]; if ([textData length] == 0) { // This condition actually causes the thing to hang, // because didFinishSpeaking doesn't get called if // nothing was spoken. So, simply return. return; } NSInteger currentRow = [_voiceTable selectedRow]; NSString *newVoice = [[NSSpeechSynthesizer availableVoices] objectAtIndex:currentRow]; [_theVoice setVoice]; BOOL spoke = [_theVoice startSpeakingString]; [speakButton setEnabled:NO]; [stopButton setEnabled:YES]; NSLog(@"Did I speak? I think %d in voice %@", spoke, [_theVoice voice]); }

If I add an assert(_voiceTable); at the beginning of sayIt, then the program aborts at that point. I had a similar problem with tableViewSelectionDidChange, where [_voiceTable selectedRow] always returned 0, and it was because _voiceTable was NIL; I figured this out by using that same assertion. I fixed that in the interface builder, but I’m not sure how I’m supposed to go about making sayIt aware of _voiceTable.

Thanks again for your help!


#4

Hi again,

I just cleaned up the _voiceTable connections in the interface builder and re-created them. All the voice stuff works now. Setting the voice in tableViewSelectionDidChange works properly.

Also, this clued me in as to why the counterField was not updating. I linked the App Delegate as the delegate for the textField. Now when controlTextDidChange gets called, the counterField text updates properly.

Focusing on that second issue, here is what I do not understand. If the App Delegate itself was not already the delegate for textField, then why was controlTextDidChange being called at all? Why was it that controlTextDidChange was being called, but it was not setting counterField, rather than controlTextDidChange simply not being called?

Really appreciate your help! Sometimes it just takes someone saying, “hey, what about this?” to finally get you to clue in on what you missed.