しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

【Swift】VoiceOver対応について調べてみる

VoiceOverについて調べてまとめてみました。

VoiceOver(ボイスオーバー)とは

VoiceOverとはiPhoneの視覚障害者の方向けの機能です。
画面を音声で読み上げることで画面が見えなくてもiPhoneを使うことができます。

f:id:llcc:20180821154214p:plain

VoiceOverをONにするとタップしたボタンやCellを読み上げてくれるようになります。
読み上げ中のオブジェクトは上画像のように黒く選択します。

VoiceOverの使い方

f:id:llcc:20180821154509p:plain

VoiceOverはiPhoneの設定アプリ → 一般 → アクセシビリティ → VoiceOverでONにすることができます。
ONにすると下のようにタップやスワイプといったジェスチャーの役目が大幅に変わります。

  • ボタンやCellをタップすると音声読み上げをする
  • 今までのシングルタップを実現したい時はボタンを2回タップ
  • スクロールしたい時は3本指でスクロール
  • 2本指でスクロールすると読み上げ可能なボタンやCellを順番に読み上げる

アプリのVoiceOver対応方法

f:id:llcc:20180821162342p:plain

VoiceOverですが、ボタンやラベルはデフォルトで読み上げ対象になっています。
上画像のようにラベル・ボタン・Viewだけを配置したアプリを作ったところ、ラベルとボタンはタップすると読み上げてくれるようになっていました。

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var myView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        myView.isAccessibilityElement = true
        myView.accessibilityLabel = "My view"
    }
}

UIViewも読み上げ対象にしたい場合はisAccessibilityElementをtrueにします。
読み上げ内容はaccessibilityLabelで設定をします。

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var myButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        myButton.isAccessibilityElement = false
    }
}

逆に読み上げたくない場合はisAccessibilityElementをfalseにします。

iPhoneシミュレータでVoiceOverの検証をする方法

iPhoneシミュレータではVoiceOverをONにすることができません。
代わりにAccessibility Inspectorというツールを使います。

f:id:llcc:20180821162837p:plain

Accessibility InspectorはXcodeのメニューバーのXcode → Open Developer Toolから開きます。

f:id:llcc:20180821163126p:plain

シミュレータの画面をタップすることで、読み上げテキストの内容などを確認することができます。

RailsとCarrierWaveで画像アップロードを実装する

CarrierWaveというGemを使って画像のアップロードを実装しました。
今回はUserモデルのimageというプロパティーに画像をセットする実装をします。

github.com

CarrierWaveのインストール

CarrierWaveを入れる前にImageMagickをインストールします。
Macの場合は下コマンドでインストールします。

brew install imagemagick@6
brew install pkg-config
export PKG_CONFIG_PATH=/usr/local/opt/imagemagick@6/lib/pkgconfig

ImageMagickを入れたらBundlerでcarrierwaveとrmagickを入れます。

gem 'carrierwave'
gem 'rmagick'

CarrierWaveでファイルアップロード

最初にファイルのアップローダーを追加します。
追加は下コマンドで行います。

rails g uploader image

コマンドを打つと下のようなファイルが生成されます。(余計なコメントなどは削除)
画像のパスやサムネイル生成の有無などを変更したい時はこのファイルに設定を書きます。

class ImageUploader < CarrierWave::Uploader::Base
  storage :file

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
end

次にUserモデルを作成します。
scaffoldで一気に作ってしまいます。

rails g scaffold user image:string

usersテーブルのimageカラムはStringのままで構いません。
ここにはファイルのパスが入ります。

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :image

      t.timestamps null: false
    end
  end
end

次はUserモデルにファイルアップロード用の処理を記述します。
user.rbに下のように1行追加します。

class User < ActiveRecord::Base
  mount_uploader :image, ImageUploader
end

次はファイルのアップロード画面を実装します。
views/users/_form.html.erbのフォーム周りの実装を修正します。

<%= form_for(@user) do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% @user.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :image %><br>
    <!-- 下2行を変更 -->
    <%#= f.text_field :image %>
    <%= f.file_field :image %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

これでファイルアップロード用画面ができました。

f:id:llcc:20180726232255p:plain

アップロードしたファイルは、ImageUploaderの設定に従ってpublic/uploads/user/image以下に保存されます。

f:id:llcc:20180726232551p:plain

CarrierWaveで画像の表示

最後にアップロードしたファイルの表示を実装します。
views/users/show.html.erbを下のように修正してください。

<p id="notice"><%= notice %></p>

<p>
  <strong>Image:</strong>
  <!-- 下2行を変更 -->
  <%#= @user.image %>
  <%= image_tag @user.image.url %>
</p>

<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

ブラウザで確認すると正しくファイルが表示されています。

f:id:llcc:20180726232822p:plain

参考URL

macOS に RMagick をインストールする
Rails 超お手軽な画像アップローダー CarrierWave の使い方 | Workabroad.jp

【iOS11】設定アプリ内にある自分のアプリの設定画面へ遷移する

iPhoneの設定アプリには、下のように自分のアプリの設定ページが用意されています。
今回はここへ直接遷移する方法について調べました。

f:id:llcc:20180720173134p:plain

自分のアプリの設定画面への遷移方法

遷移方法は下の通りです。

if let url = URL(string: UIApplicationOpenSettingsURLString) {
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
}

UIApplicationOpenSettingsURLStringは文字列型で、自分の設定ページへの遷移URLが入っています。

print(UIApplicationOpenSettingsURLString) // → app-settings:

Railsでcrontabを管理できるwheneverというGemを使ってみる

Railsを使っていると「日時バッチ」「1時間に一度の処理」など定期的に実行したいタスクが出てきます。
管理方法としては普通にcrontabに書き込んだりJenkinsなどを使ってそれらのタスクを管理する方法があります。
しかし可能ならRailsプロジェクト上で実行時間なども管理したいです。

そういった時に使えるGemがwheneverです。
これを使うと下のようにRubyでcrontabの管理ができます。

every :day, at: '12:20' do
  runner 'MyModel.execute'
end

every :sunday, at: '12:30'
  runner 'MyModel2.execute'
end

github.com

wheneverを使う準備

インストールはBundlerを使います。

gem 'whenever'

インストールが終わったらwheneverizeコマンドでwheneverの初期化をします。

wheneverize

f:id:llcc:20180716215520p:plain

実行するとconfig/schedule.rbというファイルが作られます。
ファイルの内容は下の通りです。

# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron

# Example:
#
# set :output, "/path/to/my/cron_log.log"
#
# every 2.hours do
#   command "/usr/bin/some_great_command"
#   runner "MyModel.some_method"
#   rake "some:great:rake:task"
# end
#
# every 4.days do
#   runner "AnotherModel.prune_old_records"
# end

# Learn more: http://github.com/javan/whenever

wheneverの使い方

まずは先程作られたconfig/schedule.rbに定期実行したいタスクを書きます。
下は4日おきにMyModelのcreate!を呼び出すタスクです。

every 4.days do
  runner "MyModel.create!"
end

タスクを書いたらcrontabにどんな形で書き込まれるか確認をします。
確認にはwheneverコマンドを使います。

whenever

wheneverコマンドを実行すると下のようにどのようにcrontabに書き込まれるかが表示されます。

f:id:llcc:20180716220541p:plain

crontabへの反映もwheneverコマンドを使います。
下のように--update-crontabを付けて実行します。

whenever --update-crontab

実行後crontab -lで確認するとしっかり書き込みができている事が分かります。

f:id:llcc:20180716220757p:plain

wheneverのフォーマット

wheneverではデフォルトでcommand, rake, runner, scriptというメソッドが用意されています。
実行結果はそれぞれ下の通りです。

every 4.days do
  command "bundle install" # → /bin/bash -l -c 'bundle install'
  rake "my_task" # → /bin/bash -l -c 'cd /Users/xxxx/Desktop/my_app && RAILS_ENV=production bundle exec rake my_task --silent'
  runner "MyModel.create!" # → /bin/bash -l -c 'cd /Users/xxxx/Desktop/my_app && bundle exec bin/rails runner -e production '\''MyModel.create!'\'''
  script "my_script" # → /bin/bash -l -c 'cd /Users/xxxx/Desktop/my_app && RAILS_ENV=production bundle exec script/my_script'
end

自分で上記以外のメソッドを追加する事もできます。

job_type :my_job, "cd :path && :bundle_command rake :task"

every 4.days do
  my_job "my_task" # → /bin/bash -l -c 'cd /Users/xxxx/Desktop/my_app && bundle exec rake my_task'
end

実行時間の書き方も色々な種類があります。

下のように文字列でcrontabと同じ形式に書く事ができます。
非常に分かりやすい書き方です。

every '0 0,1,2 * * *' do # → 0 0,1,2 * * *
  runner 'MyModel.execute'
end

毎日決まった時間に実行する場合は下のような書き方も分かりやすいです。

every :day, at: '12:20' do # → 20 12 * * *
  runner 'MyModel.execute'
end

特定の曜日だけ実行する事もできます。

every :sunday, at: '12:30' do # → 30 12 * * 0
  runner 'MyModel.execute'
end

「アプリ開発のためのUX講座」まとめ

「アプリ開発のためのUX講座」という動画講座がとても面白かったので、学んだ事や感想をまとめてみました。
動画はSchooの有料会員限定ですが、資料は下URLから見る事ができます。

schoo.jp

そもそもUXってなにか?

f:id:llcc:20180714160547p:plain

授業で初めて知ったんですが、実はUXはISOで定義されています。
授業では「サービスに接した時に感じるきもち」という形で噛み砕いて紹介されていました。

UXの種類

f:id:llcc:20180714161052p:plain

この授業ではUXを4種類に分けて紹介していました。
それぞれ下のような内容です。

予期的UX

サービスと接する前の体験。
「CMを見て気持ちが盛り上がる」「友達から紹介されてやりたくなる」など。

一時的UX

サービスを使って感じた体験。
「デザインがきれい」「アニメーションがおしゃれ」「特定の機能が便利だった」など。

エピソード的UX

サービス終了後の体験。
「使ってみて便利だった」「ゲームを遊んで楽しかった」など終了後に振り返る体験。

累積的UX

継続してサービスを使うことで起こる体験。
「ついつい起動してしまう」「次に使うのを心待ちにする」など。
これは一時的UXを積み重ねる事で起こる。

ユーザーに継続して使ってもらう為の工夫

f:id:llcc:20180714161839p:plain

ユーザーにサービスを継続利用してもらう為には、できるだけ強い「一時的UX」を体験してもらう事が重要だと紹介されていました。
弱い感動を連続して与えるのではなく、強い感動を与える事でファンになってくれる人が増えると話していました。

同時に、「一時的UX」をなるべく早く与える事も重要です。

「アプリ開発のためのUX講座」まとめ

「早く強い「一時的UX」を与える」という事を今まで意識してなかったので、目からウロコでした。
今まで自分は「使いにくい所を減らしてユーザーの離脱を防ぐ」って事を考えてアプリを作っていたので、これからは「どこで感動してもらうか」という事も考えようと思いました。

それからUXを4つに分類した事も面白かったです。
今までは「一時的UX」しか意識してなかったので、今後余裕があれば他のUXについても考えてみたいと思います。

はてなブログの過去記事にカテゴリーを付ける

はてなブログの過去記事にカテゴリーが全然付いてなかったので付ける事にしました。
カテゴリーなしは百記事以上あるので少しでも楽になるように工夫しつつ実施しました。

f:id:llcc:20180706221643p:plain

まずはエントリー一覧ページに移動します。

https://blog.hatena.ne.jp/llcc/llcc.hatenablog.com/entries

全ての記事を一気に直したいので「次のページ」をひたすら押して全部の記事を出します。

f:id:llcc:20180706221845p:plain

押し続けるのが大変な下コードを実行します。

$(".js-load-next-page").click()

次に「カテゴリーなし」のものだけ表示するようにします。
機能としては用意されてないので下コードを実行します。

$("tr").filter(function() { return $(this).find(".blog-category-name").length == 1 }).each(function() { $(this).remove() })

これでカテゴリーがないものだけを取得できたので地道に一括カテゴリー設定をしていきます。

f:id:llcc:20180706222824p:plain

LAContextのbiometryTypeがnoneになる問題

LAContextクラスのbiometryTypeプロパティーを使うと端末が指紋認証に対応しているか顔認証(FaceID)に対応しているかを判定する事ができます。

LAContext().biometryType == .faceID
LAContext().biometryType == .touchID

LAContextのbiometryTypeで起こった問題

しかし顔認証対応の端末で上のコードを実行してもtrueになりません。

LAContext().biometryType == .faceID // → false
LAContext().biometryType == .touchID // → false
LAContext().biometryType == .none // → true

LAContextのbiometryTypeで顔認証を判定する方法

判定するためには、canEvaluatePolicyメソッドを一度実行する必要があります。
こうすればfaceID対応している端末かを判定できます。

let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
    print(context.biometryType == .faceID) // → true
}

ドキュメントの方にもcanEvaluatePolicyが呼ばれたタイミングで値がセットされると書かれています。

This property is set when canEvaluatePolicy has been called for a biometric policy.
The default value is LABiometryTypeNone.

指紋認証だとどうなるか

iPhone8など、指紋認証対応している端末で検証しても同じような結果になりました。

LAContext().biometryType == .faceID // → false
LAContext().biometryType == .touchID // → false
LAContext().biometryType == .none // → true