How to Make a Narrated Book Using AVSpeechSynthesizer in iOS 7

Learn how to make Siri read you a bedtime story to you by using one of iOS 7’s newest features: AVSpeechSynthesizer. By .

Leave a rating/review
Save for later
Share

Your UI With Speech Control Buttons

Narrated Book with Speech Control

With the introduction of Siri, Apple has taunted developers with the implication of dynamic spoken text, but with the release of iOS 7, Apple has finally opened the door.

Introducing, AVSpeechSynthesizer. Or Siri-Synthesizer for short :]

In this tutorial, you’ll create a narrated book. Each page of the book will display text while simultaneously speaking the text. Audio narration is a splendid way to make your book app stand out from all the others on iTunes, while also accommodating those with visual impairments. Offering an audio book app can also make your work more appealing to a broader audience, since they allow people to “read” while they exercise, cook or get a little work done.

As you create your narrated book, you’ll learn:

  • How to make an iOS device speak text using AVSpeechSynthesizer and AVSpeechUtterance.
  • How to make this synthesized speech sound more natural by modifying AVSpeechUtterance properties like pitch and rate.

AVSpeechSynthesizer may not win any awards for voice acting, but you can use it relatively easily to enhance functionality in apps you develop in the future.

Note: If you are interested in developing children’s books on the iPad using Sprite Kit, check out Tammy Coron’s excellent tutorial over here: How to Create an Interactive Children’s Book for the iPad

Getting Started with AVSpeechSynthesizer

Start by downloading the Starter Project. Open the project in Xcode by navigating into the NarratedBookUsingAVSpeech Starter directory and double-clicking on the NarratedBookUsingAVSpeech.xcodeproj project file. You should see something similar to the image below:

First Open in Xcode

Build and run the project. You will see the following in the simulator:

First Run of Your App Page 1 - After Swiping Left-to-right

Your first book is a nursery rhyme about squirrels. It’s not exactly Amazon Top Selling material, but it will do for the purposes of this tutorial. Use your mouse to swipe from right-to-left in the simulator, and you’ll move to the next page as below.

Second Page of App

Use your mouse to swipe from left-to-right in the simulator, and you’ll return to the first page. Wow, you already have a functioning book. Nice work!

Understanding the Plumbing

Note: At the end of this tutorial, there are a few challenges for you. This next section covers the sample project so you can take those challenges, but if you are not interested, feel free to skip to the next section.

The starter project has two important classes:

1. Models: These store your book as a single Book object and its collection of Page objects.
2. Presentation: These present your models on the screen and respond to user interaction (e.g. swipes).

If you choose to build on this project to make your own books, its important you understand how these work. Open RWTBook.h and examine its structure.

@interface RWTBook : NSObject

//1
@property (nonatomic, copy, readonly) NSArray *pages;

//2
+ (instancetype)bookWithPages:(NSArray*)pages;
//3
+ (instancetype)testBook;

@end
  1. The pages property stores an array of Page objects, each representing a single page in the book.
  2. bookWithPages: is a convenience method to initialize and return a book with the given array of Page objects.
  3. testBook creates your book for testing purposes. You’ll start writing and reading your own books soon enough, but testBook is a simple book that is perfect to get you started.

Open RWTPage.h and examine its structure.

//1
extern NSString* const RWTPageAttributesKeyUtterances;
extern NSString* const RWTPageAttributesKeyBackgroundImage;

@interface RWTPage : NSObject

//2
@property (nonatomic, strong, readonly) NSString *displayText;
@property (nonatomic, strong, readonly) UIImage *backgroundImage;

//3
+ (instancetype)pageWithAttributes:(NSDictionary*)attributes;
@end
  1. Accesses the constants for dictionary look-ups for each page. The RWTPageAttributesKeyUtterances constant corresponds to the text on each page of the book. The RWTPageAttributesKeyBackgroundImage constant returns each background image for the page
  2. The displayText property stores the text of the page that your book presents on-screen, and the backgroundImage stores the image behind the text.
  3. pageWithAttributes: initializes and returns a page with the given dictionary of attributes.

Finally, open RWTPageViewController.m and examine its structure:

#pragma mark - Class Extension

// 1
@interface RWTPageViewController ()
@property (nonatomic, strong) RWTBook *book;
@property (nonatomic, assign) NSUInteger currentPageIndex;
@end

@implementation RWTPageViewController

#pragma mark - Lifecycle

// 2
- (void)viewDidLoad
{
  [super viewDidLoad];

  [self setupBook:[RWTBook testBook]];

  UISwipeGestureRecognizer *swipeNext = [[UISwipeGestureRecognizer alloc]
                                          initWithTarget:self
                                                  action:@selector(gotoNextPage)];
  swipeNext.direction = UISwipeGestureRecognizerDirectionLeft;
  [self.view addGestureRecognizer:swipeNext];

  UISwipeGestureRecognizer *swipePrevious = [[UISwipeGestureRecognizer alloc]
                                              initWithTarget:self
                                                      action:@selector(gotoPreviousPage)];
  swipePrevious.direction = UISwipeGestureRecognizerDirectionRight;
  [self.view addGestureRecognizer:swipePrevious];
}

#pragma mark - Private

// 3
- (RWTPage*)currentPage
{
  return [self.book.pages objectAtIndex:self.currentPageIndex];
}

// 4
- (void)setupBook:(RWTBook*)newBook
{
  self.book = newBook;
  self.currentPageIndex = 0;
  [self setupForCurrentPage];
}

// 5
- (void)setupForCurrentPage
{
  self.pageTextLabel.text = [self currentPage].displayText;
  self.pageImageView.image = [self currentPage].backgroundImage;
}

// 6
- (void)gotoNextPage
{
  if ([self.book.pages count] == 0 || self.currentPageIndex == [self.book.pages count] - 1) {
    return;
  }

  self.currentPageIndex += 1;
  [self setupForCurrentPage];
}

// 7
- (void)gotoPreviousPage
{
  if (self.currentPageIndex == 0) {
    return;
  }

  self.currentPageIndex -= 1;
  [self setupForCurrentPage];
}
@end

Here’s what this code does:

  1. The book property stores the current book and, the currentPageIndex property stores the index of the current page in book.pages.
  2. Sets up the page display once your view loads, then adds gesture recognizers so you can swipe forwards and backwards through the book’s pages.
  3. Returns the current page within the current book.
  4. Sets the book property and makes sure you start at the first page.
  5. Set up the UI for the current page.
  6. Go to the next page, if applicable, and set it up. It’s invoked by the swipeNext gesture recognizer you created in viewDidLoad.
  7. Go to the previous page, if there is one, and set it up. This is invoked by the swipePrevious gesture recognizer you created in viewDidLoad.

To Speak or Not to Speak!

That is the question.

Open RWTPageViewController.m and underneath #import "RWTPage.h", add the following line:

@import AVFoundation;

iOS speech support is in the AVFoundation framework so you must import the AVFoundation module.

Note: The @import will both import and link the AVFoundation framework. To learn more about @import as well as some other new Objective-C language features in iOS 7, check out the article: What’s New in Objective-C and Foundation in iOS 7.

Add the following line just below the declaration of the currentPageIndex property in the RWTPageViewController class extension:

@property (nonatomic, strong) AVSpeechSynthesizer *synthesizer;

You’ve just added the speech synthesizer that will speak the words in each page.

Think of the AVSpeechSynthesizer you just added to your view controller as the person doing the speaking. AVSpeechUtterance instances represent the chunks of text the synthesizer speaks.

Note: An AVSpeechUtterance can be a single word like “Whisky” or an entire sentence, such as, “Whisky, frisky, hippidity hop.”

Add the following code just before the @end at the bottom of RWTPageViewController.m

#pragma mark - Speech Management

- (void)speakNextUtterance
{
  AVSpeechUtterance *nextUtterance = [[AVSpeechUtterance alloc]
                                       initWithString:[self currentPage].displayText];
  [self.synthesizer speakUtterance:nextUtterance];
}

You’ve created an utterance to speak, and told the synthesizer to speak it.

Now add the following code just below speakNextUtterance

- (void)startSpeaking
{
  if (!self.synthesizer) {
    self.synthesizer = [[AVSpeechSynthesizer alloc] init];
  }

  [self speakNextUtterance];
}

This code initializes the synthesizer property if it’s not already initialized. Then it invokes speakNextUtterance to speak.

Add the following line of code to the very end of viewDidLoad, gotoNextPage and gotoPreviousPage

  [self startSpeaking];

Your additions ensure that speech starts when the book loads, as well as when the user advances to the next or previous page.

Build and run and listen to the dulcet tones of AVSpeechSyntesizer.

Note: If you don’t hear anything, check the volume on your Mac or iOS device (wherever you’re running the app). You might need to swipe between pages to start speech again if you missed it.

Also note: if you are running this project in the simulator, be prepared to have your console filled with cryptic error messages. This appears only to happen in the simulator. They will not print out when used on a device.

Once you’ve confirmed that you can hear speech, try building and running again, but this time, swipe from right-to-left before the first page finishes talking. What do you notice?

The synthesizer will start speaking the second page’s text once it’s completed the first page. That’s not what users will expect; they’ll expect that swiping to another page will stop speech for the current page and start it for the next page. This glitch isn’t so worrisome for short pages like nursery ryhmes, but imagine what could happen with very long pages…