文系プログラマの勉強ノート

スマホアプリ開発やデザインなどについて勉強したことをまとめています

『マジ文章書けないんだけど~朝日新聞ベテラン校閲記者が教える一生モノの文章術~』

www.daiwashobo.co.jp

書店員の友人から「タイトルの割にしっかりした内容で、ためになる」とおすすめされた本。
「マジ文章書けないんだけど」と言うほどひどくはない、…と思いたいけれど、
私も文章が上手く書けないと悩むことが多いので読んでみました。

内容の紹介

就活を控えた女子大生・すずが、文章の達人である謎のおじさんに文章の書き方を教えてもらうストーリー仕立てになっています。
最初は簡単な文法の説明から、だんだんと文章の膨らませ方、エントリーシートの書き方へと進みます。
就活生向けの内容と思いきや、就活に限定した内容は最後の15ページほどなので、意外と万人向けでした。
くだけた会話調で書かれ、可愛いイラストも多いので、本を読むのが苦手という人には読みやすいと思います。

この本が紹介する「文章術」の半分は、

  • 助詞の「は」と「が」の違い
  • 主語と述語を対応させる
  • こそあど言葉

など、とても基本的な文法の説明です。

もう半分は

  • 一つの文に一つの内容
  • 5W1H、特にWhyが重要
  • 結論から先に書く

など、これもまた一度は聞いたことがあるような基本的な内容がほとんど。
タイトル通り「マジ文章書けないんだけど」な人にわかりやすい文章の書き方、書き始め方を教える本なので、ある程度勉強している人には物足りないかもしれません。

私もほとんど聞いたことのある内容ではありましたが、改めて振り返ると結構できていないもので。
文筆のプロのような上手い文章が書けるテクニックの紹介を期待していたので、そういう意味では期待はずれでしたが、上部のテクニックや色気を出す前に、基礎基本をきちんとすることが大事ですね。

この本の良いところは、本当にありそうなダメな文章の例を出して、ダメな原因と改善例が説明されているところです。

例えば
「このバックはブランドものなので、値段と人気が高く、品質とデザインが美しいブランドだ」
という文例。
自分もこんな文を書いている、ダメなのはわかるが何がダメでどう直せばいいのか説明できないという人には一読の価値ありです。


個人的に特に参考になったのは以下のアドバイスです。

  • 一つの文に一つの内容
  • 「だが」と「ので」などのつなぎ言葉はできるだけ使わない

私は一つの文にいろいろ詰め込んで文が長くなりがちで、結局何が言いたいかが迷子になることがよくあります。
この本を読んでから、意識的に文を短くシンプルにするようになり、少し良くなった実感があります。
文を短くした方が書くのも早いですね。

書かれている内容が根本的であるだけに実践しやすく、普遍的に使える”一生モノの文章術”だと思いました。

【Xcode】XCUITest + fastlane/snapshotで始めるUIテスト(後編)

後編ではfastlane/snapshotを導入して、任意のタイミングでスクリーンショットを取得します。

前編はこちらから。
【Xcode】XCUITest + fastlane/snapshotで始めるUIテスト(前編) - 文系プログラマの勉強ノート


fastlane/snapshotとは

github.com

fastlaneは、iOS/Androidアプリのリリース作業を自動化するツール群です。
そのうちの一つであるsnapshotを使うと、UIテストをしながらストア申請時のスクリーンショットを自動作成できます。

開発環境

  • Mac OS Sierra
  • Xcode8.3
  • Swift3

導入手順

1.Xcode command line toolsをインストールする

ターミナルを起動し、Xcode command line toolsをインストールします

$ xcode-select --install
2.fastlaneをインストールする

ターミナルからRubygemsを使ってインストールします。
この他、Homebrew、Zipで入れる方法もあります

$ sudo gem update
$ sudo gem install fastlane -NV
3.snapshotを初期化する

snapshotを取りたいアプリの .xcodeproj ファイルがあるディレクトリに移動し、下記のコマンドを実行します。

$ cd /Users/xxx/XCUITestSample
$ fastlane snapshot init
$ sudo gem install fastlane -NV

下記のファイルが生成されます。

  • Snapfile
  • SnapshotHelper.swift
4.SnapshotHelper.swiftをプロジェクトに追加する

Xcodeを起動し、生成されたSnapshotHelper.swift をUIテストターゲットに追加します。
SnapshotHelper.swift は移動可なので、私はファイル本体も UIテストディレクトリ以下に移動しました。
f:id:an3714106:20170702105701p:plain

5.テストコードを編集する

前編で作成した ***UITests.swift の setUp() を修正します。

//XCUIApplication().launch()
let app = XCUIApplication()
setupSnapshot(app)
app.launch()

UIテストコード(ここではtestExample())のスクリーンショットを取得したい箇所に下記のコードを追加します。
""の文字列は任意の文字列です。画像ファイル名に使用されますので、重複しないようにします。

snapshot("ScreenShot_01")
6.Snapfileを編集する

スクリーンショットを取得したい端末と言語をSnapfileに記載します。
以下は iPhone7 の日本語の場合です。

devices([
  "iPhone 7",
  "iPhone 7 Plus",
])

languages([
  "en-US",
  "ja-JP",
])
7.fastlane/snapshotを実行する

ターミナルから下記のコマンドでfastlane/snapshotを実行します。
(snapshotを取りたいアプリの .xcodeproj ファイルがあるディレクトリで実行してください)

$ fastlane snapshot

設定ファイルやテストコードに問題がなければ、自動的にシミュレータが起動し、テストが実行されます。
完了すると screenshots/screenshots.html が表示されます。
screenshots/screenshots.html には作成されたスクリーンショットが環境別に一覧になっています。
画像は screenshots ディレクトリ以下に保存されます。

公式ドキュメントによると、ストア申請用にデバイスフレームをつけたりもできるようです。

参考URL

fastlane docs

【Xcode】XCUITest + fastlane/snapshotで始めるUIテスト(前編)

前編では、XCUITestを導入してUITestを実行してみます。

XCUITestとは

Xcode7から追加されたUIテスト機能です。
UIテストをするためのフレームワークはEarlGreyやappiumなどもありますが、
iOS8以降対応で問題なければ、XCUITestが扱いやすいと思います。

特徴

  • Xcodeに統合されている
  • アプリケーションと同じ言語で記述できる(Swift/Objective-C)
  • テストステップ毎に自動でスクリーンショットが取得される
  • アプリを操作してテストコードを作成するRecording機能がある
  • wait/sleepを入れなくても、アニメーションの待ち時間などのタイムラグをある程度上手く処理してくれる

開発環境

  • Mac OS Sierra
  • Xcode8.3
  • Swift3

導入手順

1. UI Testing Bundleを追加する

UITestを追加したいプロジェクトを開き、メニューの File > New > Target... を選択します。

iOS > Test > "iOS UI Testing Bundle" を選択して Next を押します。
f:id:an3714106:20170606221031p:plain:w500

Product Name を入力して Finish を押します。

(新規プロジェクトの場合は、作成時にプロジェクト名などを入力する画面で "Inclure UI Tests" にチェックを入れればOK)

2. テスト対象となる機能を作成する(すでにある場合は次へ)

今回はテスト用なので、テキストフィールドに文字を入力して決定ボタンを押すと
入力内容がアラートで表示される機能にしました。
f:id:an3714106:20170611111252p:plain:w250

3. Accessibility identiferを設定する(すでにある場合は次へ)

テストコードからコントロールにアクセスするために、Accessibility identiferを設定します。
コードから追加する方法もありますが、ここではIBを開いてコントロールを選び、右側のペインの
Identity inspector(左から3番目) > Accessibility > identifer
に一意の文字列を設定します。
f:id:an3714106:20170611112328p:plain

※Accessibility identiferが保存されない場合※
稀にAccessibility identiferを設定しても、実行時に保存されていないことがあります。
その場合、Accessibility identifer設定後に、座標やサイズなどレイアウト情報を変更すると保存されました。
(おそらくXcodeのバグ。保存されたあとはレイアウト情報は戻して大丈夫です。)

4. テストコードを作成する

先程追加したUITestTarget内のswiftファイルにテストコードを書きます。
(デフォルトでは"[ProjectName]UITests.swift"というファイル名になっていると思います。)

初期状態で「testExample()」というメソッドが作成されていますが、
このように「test〜」で始まるメソッドがテスト時に実行されるコードになります。
f:id:an3714106:20170611113555p:plain

testExample()内にテストコードを作成します。
"textField"、"decisionButton"は先程設定したAccessibility identiferです。

func testExample() {

    let app = XCUIApplication()

    // 文字を入力
    let textField = app.textFields["textField"]
    textField.tap()
    textField.typeText("UIテストを実行")

    // 決定ボタンをタップ
    let button = app.buttons["decisionButton"]
    button.tap()

    // 入力内容がアラートに表示される
}
5. テストを実行する

メニューの Product > Test を選択するか、テストメソッド名の左側の◇ボタンを押してテストを実行します。
◇ボタンが緑色になれば成功、赤くなれば失敗です。
f:id:an3714106:20170611121054p:plain:w400

6. テスト結果を確認する

左側のペインの Report navigator(一番右) から実行したテストを選ぶと、テスト結果の詳細が表示されます。
f:id:an3714106:20170611124452p:plain

XCUITestは自動でスクリーンショットを生成してくれます。
▼ボタンを押してツリーを開き、目のアイコンを押すとスクリーンショットを確認できます。
f:id:an3714106:20170611124939p:plain
f:id:an3714106:20170611124950p:plain:w500

もし任意のタイミングでスクリーンショットを取得したい、スクリーンショットをまとめて見たいという場合は
fastlane/snapshotを併用することで実現できます。

というわけで、後編ではfastlane/snapshotを導入していきます。

【Xcode】コードだけでグラデーション作成

下図のようなグラデーションをコードだけで作る方法です。
f:id:an3714106:20170611133939p:plain:w250

override func viewDidLoad() {
    super.viewDidLoad()

    let colors = [UIColor(red: 112/255, green: 134/255, blue: 241/255, alpha: 1.0).cgColor,
                  UIColor(red: 40/255, green: 169/255, blue: 255/255, alpha: 1.0).cgColor,
                  UIColor(red: 180/255, green: 225/255, blue: 255/255, alpha: 1.0).cgColor]
    let gradientLayer = gradientLayerWith(frame: view.bounds, colors: colors)
    view.layer.addSublayer(gradientLayer)
}

func gradientLayerWith(frame: CGRect, colors: Array<Any>) -> CAGradientLayer {

    let layer = CAGradientLayer()
    layer.frame = frame
    layer.colors = colors

    // グラデーションの向きを横にしたい場合
    layer.startPoint = CGPoint(x: 0, y: 0.5)
    layer.endPoint = CGPoint(x: 1, y: 0.5)

    return layer
}

【Xcode】UIAlertControllerで簡単進捗ダイアログ作成

カスタムビューを作らず、UIAlertControllerで簡単な進捗ダイアログを表示する方法です。

f:id:an3714106:20170213234543p:plain

// インジケータ表示
alert = UIAlertController(title: "Loading...", message: "\n", preferredStyle: .alert)

let indicator = UIActivityIndicatorView()
indicator.translatesAutoresizingMaskIntoConstraints = false
alert.view.addSubview(indicator)

let views: [String: UIView] = ["alert": alert.view, "indicator": indicator]
var constraints = NSLayoutConstraint.constraints(withVisualFormat: "V:[indicator]-(12)-|",
                                                 options: [],
                                                 metrics: nil,
                                                 views: views)
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[indicator]|",
                                              options: [],
                                              metrics: nil,
                                              views: views)
alert.view.addConstraints(constraints)

indicator.isUserInteractionEnabled = false
indicator.color = UIColor.lightGray
indicator.startAnimating()

present(alert, animated: true, completion: {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        self.alert.dismiss(animated: true, completion: {
            self.alert = nil
        })
    }
})


こちらのサイトの回答を参考にSwift3.0の文法で書き直し、インジケータの位置を調整しました。
アラートの高さをmessageの改行で調整しているのが少々ださいですが、
カスタムビューを作らなくて良いのでお手軽ですね。
stackoverflow.com

【Xcode】UIAlertControllerの外側をタップで閉じる & Toast風に一定時間後に閉じる方法

UIAlertControllerの外側をタップで閉じる

f:id:an3714106:20170208205827p:plain

UIAlertControllerで、外側(上の図でグレーの部分)をタップすると閉じる方法です。

class ViewController: UIViewController {
    
    var alert: UIAlertController!

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        // 外側をタップで非表示
        alert = UIAlertController(title: "Alert", message: "Tap outside of alert and dismiss", preferredStyle: .alert)
        present(alert, animated: true, completion: {
            self.alert.view.superview?.isUserInteractionEnabled = true
            self.alert.view.superview?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.closeAlert)))
        })
    }
    
    func closeAlert() {
        alert.dismiss(animated: true, completion: nil)
        alert = nil
    }
}


一定時間後に消えるToast風AlertController

AndroidのToastのように、タップしなくても一定時間後に消える方法です。

class ViewController: UIViewController {
    
    var alert: UIAlertController!

    override func viewDidAppear(_ animated: Bool) {
        
        super.viewDidAppear(animated)
        
        // 一定時間後に非表示
        alert = UIAlertController(title: nil, message: "This alert will disappear after 3 sec", preferredStyle: .alert)
        present(alert, animated: true, completion: {
            self.perform(#selector(self.closeAlert), with: nil, afterDelay: 3.0)
        })
    }
    
    func closeAlert() {
        alert.dismiss(animated: true, completion: nil)
        alert = nil
    }

Toast風の方はExtension化しておけば使い手がありそう。

Speech Synthesis API を使って動的に音声を読み上げる

Web Speech APIの一つ、Speech Synthesis API を使って動的に音声読み上げさせるサンプルです。
最近の標準的なブラウザで使用でき、音声データを用意することなく読み上げ機能を実現できます。

動作サンプル



サンプルコード

<!DOCTYPE html>
<html>
<head>
    <title>音声読み上げサンプル</title>
    <meta charset="UTF-8">
</head>
<body>
    <p>
        <input id="text" type="text" size="60" value="吾輩は猫である。名前はまだない。">
        <button id="play">音声読み上げ</button>
    </p>

    <script>
        document.getElementById('play').addEventListener('click', function(){
            var text = document.getElementById('text').value;
            speak(text);
        });

        var tmp;
        
        function speak(text) {
            // 再生中の音声をキャンセル
            speechSynthesis.cancel(tmp);
            
            // 日本語音声で読み上げ
            var speech = new SpeechSynthesisUtterance();
            speech.lang = 'ja-JP';
            speech.text = text;
            speechSynthesis.speak(speech);
            
            tmp = speech
        }
    </script>
</body>
</html>

使ってみた感想

・OSやブラウザによって使用できる合成音声の種類が違うようです。
・Edge、FirefoxChromeSafariOperaiOS SafariChrome for Androidで使用できます。
・イントネーションや漢字の読み方はまだ微妙です。OSよって違う読み方をする場合もあります。
 漢字の読みについては平仮名表記にした方が確実そうです。
 イントネーションは、間に読点(、)を挟むことで多少調整できます。