cockscomblog?

cockscomb on hatena blog

子供にアプリを作る

3歳の息子にiPad Proを与えている。2018年の11インチのiPad Proで、僕のお下がりだ。Apple Pencilも与えてあるが、こちらは1歳になったばかりの娘が狙っているため、恐れた息子はApple Pencilをなるべく片付けておく。

自由に使っていい

iPad Proを与えたからといって、3歳の息子がそれで何か大層なことをするということはない。退屈なときにYouTube Kidsで何かを見ていることが多い。ときどきGarageBandiRig Keys 2で音を出して遊んだり、ProcreateApple Pencilで謎の絵を描いたりしている。こういうのは大人も一緒にやってあげると喜ぶ。Smart Keyboardをくっつけて「ブログを書いてる」と宣ったりもする。あとは週末に祖父母とFaceTimeをする。

とにかくiPadを自由に使わせている。自由に使えない道具に関心を持ち続けるのは難しかろうから、最低限ペアレンタルコントロールを設定して、あとはなるべく好きにさせる。

悪い大人

大人というのは悪いから、ひらがなのキーボードを見せて、これで自分の名前を入力してみなさい、と勧めたりもする。もちろんそこには、文字を覚えさせようという魂胆がある。息子も素直なもので、喜んで従う。

あるとき息子が自分の名前を入力したというので見せてもらうと、そこには逆さまに並んだ名前があった。ふつうに入力するより難しいだろうに、子供というのは不思議だ。

その晩、妻が一計を案じて、入力した文字が読み上げられたらいいのではないか、と言う。しからばと思い、僕は自慢のMacBook Pro (13-inch, M1, 2020)を取り出した。

アプリを作った

f:id:cockscomb:20201215194655j:plain
写真を撮ろうとしたら娘がいたずらしにきた

AppleプラットフォームではAVSpeechSynthesizerというのを使うと、テキストを読み上げさせられる*1。近頃はSwiftUIがあるので、UIを作るのも簡単。

アプリのコードはこういう感じ。コピペしたら動くと思う。

import SwiftUI
import AVFoundation

@main
struct SpeechApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

struct ContentView: View {
  @StateObject var speaker = Speaker()

  var body: some View {
    VStack {
      TextField("Text", text: $speaker.text)
        .font(Font.system(size: 36))
        .textFieldStyle(RoundedBorderTextFieldStyle())
        .padding()

      Button(action: speaker.speech) {
        Label("Play", systemImage: "play.fill")
          .font(Font.system(size: 36))
      }
      .padding()
      .disabled(speaker.isSpeaking)

      GroupBox(label: Label("Speed",
                            systemImage: "speedometer")) {
        Slider(
          value: $speaker.rate,
          in: Speaker.minimumRate...Speaker.maximumRate,
          minimumValueLabel: Image(systemName: "tortoise"),
          maximumValueLabel: Image(systemName: "hare")) {
        }
        .disabled(speaker.isSpeaking)
      }
      .padding()
    }
  }
}

class Speaker: NSObject, ObservableObject {
  static let defaultRate = AVSpeechUtteranceDefaultSpeechRate
  static let minimumRate = AVSpeechUtteranceMinimumSpeechRate
  static let maximumRate = AVSpeechUtteranceMaximumSpeechRate

  private let synth = AVSpeechSynthesizer()

  @Published var text: String = ""
  @Published var rate: Float = defaultRate
  @Published private(set) var isSpeaking: Bool = false

  override init() {
    super.init()
    synth.delegate = self
  }

  func speech() {
    let utterance = AVSpeechUtterance(string: text)
    utterance.voice = AVSpeechSynthesisVoice(language: nil)
    utterance.rate = rate
    synth.speak(utterance)
  }
}

extension Speaker: AVSpeechSynthesizerDelegate {
  func speechSynthesizer(
    _ synthesizer: AVSpeechSynthesizer,
    didStart utterance: AVSpeechUtterance
  ) {
    isSpeaking = synthesizer.isSpeaking
  }
  func speechSynthesizer(
    _ synthesizer: AVSpeechSynthesizer,
    didFinish utterance: AVSpeechUtterance
  ) {
    isSpeaking = synthesizer.isSpeaking
  }
}

10分くらいでプロトタイプができて、見かけをちょっとマシにしたり、読み上げ速度を変えられるようにしたり、実機にインストールしたりで、トータルで2時間くらいかかっている。

子供にアプリを与える

翌朝、息子にアプリの入ったiPadを見せた。尊敬されたいから、お父ちゃんが作ったアプリだよ、とアピールした。息子もなんとなく嬉しそうに遊んでくれる。いい子だ。

最初は自分の名前を読み上げさせたりしていたが、すぐに滅茶苦茶な文字列を読み上げさせる方が楽しいことに気づいた。子供は自由。

ということで、必ずしも思い通りにいくわけではないが、悪くはない。子供にアプリを作るのは新しい感じがある。最近はApple Developer Programに加入していなくても、とりあえず実機にインストールするくらいはできるようになっている。加入すればAd hocに配信できるし、なんならApp Storeでリリースもできる。僕は加入している。

ということで、子供にiPadを与えたり、SwiftUIで気楽にアプリを作るのは楽しい。

最新 Apple iPad Air (10.9インチ, Wi-Fi, 64GB) - シルバー (第4世代)

最新 Apple iPad Air (10.9インチ, Wi-Fi, 64GB) - シルバー (第4世代)

  • 発売日: 2020/10/23
  • メディア: Personal Computers

*1:Webの技術で作りたければWeb Speech APIという選択肢もあると思う。