- 投稿日:2019-09-29T23:02:32+09:00
FlutterとCloud Functions、Firebase Authを使ってSign In with Appleを実装してみた
どうやら Sign in with Appleはサードパーティログインを実装している場合は必須で実装しないといけないみたいですね。
https://qiita.com/_asa08_/items/4f6e383fc6c9d38046ed
自分のアプリではログインの処理はFlutterとFirebaseで完結させたかったので実装してみました。
完成系はこんな感じです。(あくまで参考に)
Sign In with Appleはこんな感じ。
— shogo.yamada (@yshogo87) September 29, 2019
ユーザー体験が最高だから基本は導入したいけど、別プラットフォームで同じアカウントでログインしたい時どうしたらいいんだろう? pic.twitter.com/cZXTQbOrGxまずは準備
まずはXcodeの設定をしないといけないので、こちらの素晴らしい記事を参考に実装してください。
https://qiita.com/_asa08_/items/4f6e383fc6c9d38046ed
この記事では割愛します。
Flutter側の準備
次にFlutter側に Sign In with Appleのプラグインがあったので、こちらを導入します。
https://pub.dev/packages/apple_sign_in
pubspec.ymlに下記を追加(バージョンについては最新のものをお使いください。)
apple_sign_in: ^0.0.3ボタンを出す
それではログインボタンを実装します。
実装方法は下記import 'dart:io'; import 'package:apple_sign_in/apple_sign_in.dart'; class MyPage extends StatefulWidget { @override _MyPageState createState() => _MyPageState(); } class _MyPageState extends State<MyPage> with RouteAware { final Future<bool> _isAvailableFuture = AppleSignIn.isAvailable(); @override void initState() { super.initState(); AppleSignIn.onCredentialRevoked.listen((_) { print("Credentials revoked"); }); } @override Widget build(BuildContext context) { return _login(); } Widget _login() { return Scaffold( backgroundColor: Colors.black, appBar: AppBar(), body: Container( width: MediaQuery.of(context).size.width, child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Platform.isIOS ? FutureBuilder( future: _isAvailableFuture, builder: (_, isAvailableSnapshot) { if (!isAvailableSnapshot.hasData) return SizedBox(); return Container( margin: const EdgeInsets.only( left: 50, right: 50, ), child: isAvailableSnapshot.data ? AppleSignInButton( onPressed: () { // ログイン処理を実装する } : SizedBox(), ); }, ) : const Text("This Platform is the iOS") ], ), ), ); } }これでボタンが表示されます。下がボタンが表示された例です。
お、FlutterでSign in with Apple実装できそうだ? pic.twitter.com/Wj06Dmc8h7
— shogo.yamada (@yshogo87) September 27, 2019もしかしたらこのレイアウトだと、Appleの審査に弾かれてしまう可能性があるのでガイドラインを読んで適宜修正してください。
https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/リクエスト処理
次にリクエスト処理を実装します。
Future doAppleLogin() async { final AuthorizationResult result = await AppleSignIn.performRequests([ AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName]) ]); switch (result.status) { case AuthorizationStatus.authorized: print("success"); print(result.credential.user); // ログイン成功 break; case AuthorizationStatus.error: print("Sign in failed: ${result.error.localizedDescription}"); throw Exception(result.error.localizedDescription); break; case AuthorizationStatus.cancelled: print('User cancelled'); break; } }Cloud Functionsの実装
次にカスタム認証するためのトークンの取得をCloud Functionsで行います。
こちらのコードは下記の記事を参考に実装しました。(下の記事のコードの方がとても綺麗で安全なコードですので、そっちを参考にしたほうがいいと思います)https://qiita.com/fromkk/items/573ed96bc6a367bbf167
import * as functions from "firebase-functions"; import * as admin from "firebase-admin"; admin.initializeApp(); export const onSignInWithApple = functions.https.onCall( async (data, context) => { const uid = data.userIdentifier; const email = data.email; const customToken = await admin.auth().createCustomToken("Apple" + uid); if (!email) { return { custom_token: customToken }; } await admin.auth().updateUser(uid, { email: email }); return { custom_token: customToken }; } );書いたらデプロイします。
Flutter側から呼び出し
次にこのエンドポイントをFlutterから呼び出します。
エンドポイントですので、普通にAPIを叩くようにしてもいいのですが、Cloud Functionsを呼び出すプラグインがあるので、せっかくなのでこちらを使いましょうhttps://pub.dev/packages/cloud_functions
pubspec.ymlに下記を追加(バージョンについては最新のものをお使いください)
cloud_functions: ^0.4.1+1ログインが成功したらCloud Functionsを呼び出します。
Future doAppleLogin() async { final AuthorizationResult result = await AppleSignIn.performRequests([ AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName]) ]); switch (result.status) { case AuthorizationStatus.authorized: print("success"); print(result.credential.user); CloudFunctions functions = CloudFunctions.instance; HttpsCallableResult httpCallResult = await functions .getHttpsCallable(functionName: "onSignInWithApple") .call({ "email": result.credential.email, "userIdentifier": result.credential.user, }); FirebaseUser user = await _signInToFirebaseFromApple( httpCallResult.data["custom_token"]); // ログイン後の処理 break; case AuthorizationStatus.error: print("Sign in failed: ${result.error.localizedDescription}"); throw Exception(result.error.localizedDescription); break; case AuthorizationStatus.cancelled: print('User cancelled'); break; } } Future<FirebaseUser> _signInToFirebaseFromApple(String token) async { FirebaseAuth auth = FirebaseAuth.instance; final result = await auth.signInWithCustomToken(token: token); return result.user; }これで実装完了です。
追記
もしここでCLoud Functionsを呼び出した時にエラーが発生した場合はIAM関連で必要な権限が設定されていない可能性があります。
下記のツイートで解決しますので、詳しくはこちらをどうぞGoogle App Engine Node.jsでGoogle Cloud StorageのSigned URLを取得する by @ogawa0071 https://t.co/EGPkuqMVsb
— shogo.yamada (@yshogo87) September 29, 2019
この方法で解決しました!
こんな権限いじらないといけないんか、、、
めっちゃハマった、、、 https://t.co/eOopZtOvpZ今不明なこと
Firebase にカスタム認証したときにSign In with Appleでメールアドレスを公開しない設定にしてるときにサインアップするとFirebaseのコンソールでの表示がこういう風になってしまうんだけど、本当にこれでいいのかな。 pic.twitter.com/uc9m5A96xx
— shogo.yamada (@yshogo87) September 29, 2019このツイートについてもし知ってる方いらっしゃれば、コメント欄にてご連絡ください。
- 投稿日:2019-09-29T21:35:23+09:00
【SwiftUI】画面いっぱいにViewを広げる
問題を感じたView
このViewを横いっぱい広げたいなんとかしたい。
struct DailyCaloriesSummaryView: View { var body: some View { VStack(alignment: .leading, spacing: 5) { Text("9月11日") .font(.headline) VStack(alignment: .leading, spacing: 5) { Text("摂取カロリー: 1800kcal") Text("消費カロリー: 600kcal") Text("差分: 1200kcal") } .font(.body) .padding(.leading, 15) } .background(Color(.secondarySystemFill)) } } struct DailyCaloriesSummaryView_Previews: PreviewProvider { static var previews: some View { DailyCaloriesSummaryView() } }画面いっぱい広げる
.background
の手前に以下のコードを差し込む.frame(maxWidth: .infinity)画面いっぱいまで広げたうえで、左寄せする
.frame(maxWidth: .infinity, alignment: .leading)見た目を整えて最終形
struct DailyCaloriesSummaryView: View { var body: some View { VStack(alignment: .leading, spacing: 5) { Text("9月11日") .font(.headline) VStack(alignment: .leading, spacing: 5) { Text("摂取カロリー: 1800kcal") Text("消費カロリー: 600kcal") Text("差分: 1200kcal") } .font(.body) .padding(.leading, 15) } .frame(maxWidth: .infinity, alignment: .leading) .padding(15) .background(Color(.secondarySystemFill)) .cornerRadius(15) } } struct DailyCaloriesSummaryView_Previews: PreviewProvider { static var previews: some View { DailyCaloriesSummaryView() .padding(15) } }
- 投稿日:2019-09-29T20:14:18+09:00
【SwiftUI】ListのSeparatorを無理やり消したい時の解決法
2019/09/28日のSwiftUI1の現状で、SwiftUIのList separatorをインスタンス単位で公式に消して使う方法がなく、無理やりな方法を使うか、回避策を取る必要があります。
この記事では、上記の2つの方法について共有します。
1. 無理やり解決する
出典不明ですが、SwiftUIのListも
UITableView.appearance()
の制御下にあるようなので、この挙動を切り替えます。Viewの表示/非表示で切り替えても良いですし、場合によってはUIApplicationDelegateで切り替えても良いかもしれません。
struct ContentView: View { var body: some View { NavigationView { List(accounts) { account in YourView() } } .onAppear { // SwiftUIの初期バージョンでは、Listに対してSeparatorを取り外す処理ができないので、この特殊なコードで無理やり消す。必要になったら別途考える。 UITableView.appearance().separatorColor = .clear } .onDisappear { // Viewが画面を離れるときに元に戻す UITableView.appearance().separatorColor = .separator } } }2. ScrollViewを使う
こちらもStackoverflowだとこの辺に書いてあるのと、Twitterでも先駆者が何人か検証されていました。
ScrollView { VStack(alignment: .leading) { ForEach(accounts) { account in DailyCaloriesSummaryView() } } }どっち使ったら良い?
超暫定対応になるので、自分が期待する挙動を楽に実現できる方で進めれば良いかと思います。// FIXME: を残しておいて、正式対応されたらすぐに対応しても良いぐらいのやつ。
- 投稿日:2019-09-29T19:05:20+09:00
Swiftでルビを表示させたい件について
どうでもいいこと
先日iOSDCに参加して以降ためこんでいた発信欲をそろそろ解放したいと思い、例によって備忘録でしかない内容をiOSエンジニア初心者の自分のために書きます。(ぜひ来年はLT枠とかで登壇したい...!)
Swiftでルビを表示させたい初心者へ
今回は仕事で少し触れそうなので、ルビ表示をSwiftで実装する、というテーマです。
ほとんど自分のためとはいえ、私のような初心者のためになることもあるのではという前向きな気持ちも抱きながら書いています。
以下の2つの記事を全面的に参考にさせていただきましたが、こちらで簡潔にまとめられている説明を、わざわざくどく、面倒くさく、まどろっこしく初心者向けにしたものがこの記事です。
(ルビ表示の実装が初心者に需要ある?という自分へのツッコミをスルーしながら書いてます。)
- https://qiita.com/woxtu/items/284369fd2654edac2248
- https://qiita.com/negi0205/items/6c73128ff2cf680df47c
実装したコードがこちら
実装の主な方針は以下の通りです。
- 前提として
- 環境:Xcode10.3、Swift5
- 青空文庫の表記(「紅玉《ルビー》」)をお借りしてルビ付文字のStringを表現
- 正規表現を使ってString内のルビ付文字を特定 → StringExtension.swiftを参照
- CTRubyAnnotationとNSAttributedStringを使ってルビを生成 → StringExtension.swiftを参照
- カスタムUILabelクラスを作ってルビを表示 → RubyLabel.swiftとRubyViewController.swiftを参照
ではさっそく、正規表現を使ってルビ付文字を特定し、ルビを生成する実装です。
StringExtension.swiftimport UIKit extension String { // 文字列の範囲 private var stringRange: NSRange { return NSMakeRange(0, self.utf16.count) } // 特定の正規表現を検索 private func searchRegex(of pattern: String) -> NSTextCheckingResult? { do { let patternToSearch = try NSRegularExpression(pattern: pattern) return patternToSearch.firstMatch(in: self, range: stringRange) } catch { return nil } } // 特定の正規表現を置換 private func replaceRegex(of pattern: String, with templete: String) -> String { do { let patternToReplace = try NSRegularExpression(pattern: pattern) return patternToReplace.stringByReplacingMatches(in: self, range: stringRange, withTemplate: templete) } catch { return self } } // ルビを生成 func createRuby() -> NSMutableAttributedString { let textWithRuby = self // ルビ付文字(「|紅玉《ルビー》」)を特定し文字列を分割 .replaceRegex(of: "(|.+?《.+?》)", with: ",$1,") .components(separatedBy: ",") // ルビ付文字のルビを設定 .map { component -> NSAttributedString in // ベース文字(漢字など)とルビをそれぞれ取得 guard let pair = component.searchRegex(of: "|(.+?)《(.+?)》") else { return NSAttributedString(string: component) } let component = component as NSString let baseText = component.substring(with: pair.range(at: 1)) let rubyText = component.substring(with: pair.range(at: 2)) // ルビの表示に関する設定 let rubyAttribute: [CFString: Any] = [ kCTRubyAnnotationSizeFactorAttributeName: 0.5, kCTForegroundColorAttributeName: UIColor.darkGray ] let rubyAnnotation = CTRubyAnnotationCreateWithAttributes( .auto, .auto, .before, rubyText as CFString, rubyAttribute as CFDictionary ) return NSAttributedString(string: baseText, attributes: [kCTRubyAnnotationAttributeName as NSAttributedString.Key: rubyAnnotation]) } // 分割されていた文字列を結合 .reduce(NSMutableAttributedString()) { $0.append($1); return $0 } return textWithRuby } }ルビの表示に関する設定で出てきたコードに説明を加えます。
let rubyAttribute: [CFString : Any] = [ kCTRubyAnnotationSizeFactorAttributeName: // ベース文字(漢字など)に対するルビサイズの割合 kCTForegroundColorAttributeName: // ルビの色 ] let rubyAnnotation = CTRubyAnnotationCreateWithAttributes( alignment: /* ベース文字に対するルビの整列形式を指定 .auto = CoreTextが調整?(よくわかってないです...上手くやってくれるっぽい) .start = ベース文字と前を揃えて配置 .center = ベース文字と中心を揃えて配置 .end = ベース文字と後ろを揃えて配置 .distributeLetter = ベース文字と前と後ろが揃うように均等配置 .distributeSpace = ルビの文字間が均等になるように配置 .lineEdge = ベース文字と前を揃えて文字間を詰めて配置 */ overhang: /* ベース文字よりルビが長い場合のルビのはみ出し可否を指定 .auto = ベース文字の前後へはみ出し可能 .start = ベース文字の前へのはみ出しのみ可能 .end = ベース文字の後ろへのはみ出しのみ可能 .none = ベース文字からのはみ出し不可 */ position: /* ベース文字に対するルビの位置を指定 .before = ベース文字の上にルビを横表示 .after = ベース文字の下にルビを横表示 .inLine = ベース文字の後ろにルビを横表示 .interCharacter = ベース文字の後ろにルビを縦表示 */ string: // 表示するルビの文字列 attributes: // 表示するルビの文字列に設定済みのattributes )これでルビの生成は終了です。
最後にUILabelをカスタムしてルビを表示させる実装です。
RubyLabel.swiftimport UIKit class RubyLabel: UILabel { // ルビを表示 override func draw(_ rect: CGRect) { // 描画位置を設定 guard let context = UIGraphicsGetCurrentContext() else { return } context.translateBy(x: 0, y: rect.height) context.scaleBy(x: 1.0, y: -1.0) // ルビを挿入 guard let attributedText = self.attributedText else { return } let frame = CTFramesetterCreateFrame( CTFramesetterCreateWithAttributedString(attributedText), CFRangeMake(0, attributedText.length), CGPath(rect: rect, transform: nil), nil) // 描画に反映 CTFrameDraw(frame, context) } }(グラフィックスコンテキスト周りの理解はまだまだ浅い...)
ルビ表示が途切れてしまう場合はUILabelのサイズを調整するなどします。ルビが表示されました!
参考までにViewController(UILabelは上記のカスタムクラスを継承)はこんな感じにしています。
RubyViewController.swiftimport UIKit class RubyViewController: UIViewController { @IBOutlet var rubyLabel: RubyLabel! override func viewDidLoad() { super.viewDidLoad() setUpLabel() } private func setUpLabel() { rubyLabel.attributedText = "|紅玉《ルビー》がほしい".createRuby() rubyLabel.textAlignment = .center rubyLabel.font = .systemFont(ofSize: 30.0) } }
- 投稿日:2019-09-29T17:41:07+09:00
LaunchScreen Storyboardの多言語化
スプラッシュウィンドウをこれまでLaunchImagesで作成していた方も多いかと思いますが、iPad OS登場以降はiPadの画面分割への対応からLaunchScreen.storyboard(もしくはLaunchScreen.xib)の使用が推奨されるようになりました。
アプリが複数の言語に対応している場合、スプラッシュウィンドウも多言語化(ローカライズ)する必要がありますので、今回はLaunchScreen.storyboardの多言語化の方法を調べてみました。結論・・・意外と簡単
LaunchScreen.storyboardに表示する画像を言語ごとに分けることはできなかったのですが、起動スプラッシュに使用するStoryboardの名称がInfo.plistに設定される様になっている為、Info.plistを多言語化して言語ごとに使用するStoryboardを変えるようにすれば簡単にできました。
手順
環境:Xcode 11(といっても今回の手順はXcodeのバージョンには依存しません)
今回のサンプルプログラムでは英語と日本語に対応するものとします。Xcodeの[PROJECT] - [Info]にある[Localizations]に英語と日本語を設定します。
Assets.xcassetsに英語用のスプラッシュウィンドウの画像と日本語用のスプラッシュウィンドウの画像を登録します。
まずデフォルトで存在するLaunchScreen.storyboardは英語用のスプラッシュウィンドウにします。LaunchScreen.storyboardのViewControllerにUIImageViewを追加し英語用のスプラッシュウィンドウの画像を設定します。
次に日本語用のスプラッシュウィンドウを表示する為のStoryboardを追加します。Xcodeの[File]-[New]-[File...]を選択しダイアログで[Storyboard]を選択し[Next]を押下します。
Storyboardの名称は[LaunchScreen_Japanese.storyboard]として[Create]を押下します。
作成されたLaunchScreen_Japanese.storyboardにUIViewControllerを追加し、[is Initial View Controller]にチェックを入れます。そしてUIImageViewを追加し日本語用のスプラッシュウィンドウの画像を設定します。
これで英語用のスプラッシュウィンドウを表示するStoryboardと日本語用のスプラッシュウィンドウを表示するStoryboardの用意ができました。次からは言語毎にStoryboardを使い分ける設定を行います。Xcodeの[TARGETS]の[App Icons and Launch Images]で[Launch Screen File]が[LaunchScreen]になっていることを確認します。
この状態でInfo.plistを見るとLaunchScreen.storyboardの名称がUILaunchStoryboardNameというキーに設定されていることが分かります。
ということで、このUILaunchStoryboardNameの値を言語毎に変えられる様にします。Info.plistを多言語化する為のInfoPlist.stringsを追加します。Xcodeの[File]-[New]-[File...]を選択しダイアログで[Strings File]を選択し[Next]を押下します。
[Do you want to localize this file?]と聞かれますので日本語を選択して[Localize]を押下します。
InfoPlist.stirngsに以下のキーと値を設定します。
"UILaunchStoryboardName" = "LaunchScreen_Japanese";
これで日本語のときだけスプラッシュウィンドウにLaunchScreen_Japanese.storyboardが使用され、それ以外の言語の時はデフォルトのLaunchScreen.storyboardが使用される様になりました。
では動かしてみましょう。
参考文献
- 投稿日:2019-09-29T12:22:32+09:00
Human Interface Guidelines App Architecture まとめ
1. Loading ローディング
アプリが何か処理をしている際に画面が動かなかったり、真っ白になるとアプリがフリーズしたように見えてしまい、ユーザーがイライラしてアプリを終了したりアンインストールする可能性が上がるためロードしていることをユーザーに示す必要がある。
ローディング画面を作る
方法1
Loading Spinner(以下の写真のようなクルクルしたやつ)を表示する。
方法2
方法3
2. Modality (分岐型)
モーダリティーはユーザの注目を集めるデザインの手法です。特定の処理をするときに、別画面やViewを前の画面に重ねて表示する手法です。例えばAir Dropするときに下から出てくる画面のようなものを指します。
手法1:アラートビュー
このように画面の上にビューを重ねることでユーザーの注目を引くことができます。手法2:モーダルビュー
手法3:シートビュー
これらを使うときの注意点
これらの手法をむやみやたらに使ってもユーザーが使いごごちのいいアプリを作ることはできません。
以下のことを心がけて使用する必要があります。モーダルビューは本当に必要な時にしか使わない。
モーダルビューはユーザが今までしていたタスクとは違うことをしてもらう時に使います。なのでユーザーが本当にその作業をすべきなのか考え、必要なときに使いましょう。
アラートビューは本当に必要な時のみ使う。
アラートビューは使いやすく、むやみやたらに使われやすい傾向がありますが、むやみやたらに表示するとユーザーの操作を邪魔してしまうので本当に必要な時にのみ使いましょう。
モーダルビューの内容、することは少なめに
モーダルビューでたくさんのことをユーザーにしてもらうと、ユーザーが何をするためにモーダルビューを表示させたのかわかりにくくなってしまう傾向があります。なので、モーダルビューのコンテンツは極力少なくしましょう。そして、完了(Done)ボタンは本当にユーザーがタスクを終わらせた時のみに使いましょう。
モーダルビューにキャンセルボタンを必ずつけましょう。
モーダルビューを表示する際にそれをまたしまえるようにキャンセルボタンを設置しましょう。
ユーザーが入力したデータが消えるのを防ぎましょう。
ユーザーが何かをモーダルビューで入力した際に間違えてキャンセルジェスチャーやキャンセルボタンを押してしまう可能性があります。モーダルビューがキャンセルされる前にはデータが消えてしまうことをユーザーに必ず示しましょう。
ポップオーバーの上には何も表示しない。
ポップオーバーの上にはカードなどを表示することができますが、表示する前にはポップオーバーをしまってから表示するようにしましょう。
ポップオーバー
モーダルビューにナビゲーションバーなどをつける場合は元のビューと同じデザインに
モーダルビューの表示のアニメーションは適切なものを使いましょう。
3ナビゲーション
階層ナビゲーション
一つの画面に選択する項目を一つしか置かない手法。初めの画面に戻るには手順を画面を一つずつ遡っていく必要がある。メールや設定などのアプリはこの手法を使っている。
フラットナビゲーション
全ての画面からタブバーなどを使い全ての画面にアクセスできるナビゲーション。ミュージックアプリやApp Storeなどで使われいる。
コンテンツ主従型
基本的には使われない型だが、ゲームなどではこのようなナビゲーション型を使う場合がある。
引用元
Apple Human Interface Guidelines (かなり意訳してまとめました。)
- 投稿日:2019-09-29T04:42:22+09:00
Apple iOS Human Interface Guideline を読み込む【App Architecture編】
TL;DR
AppleのHuman Interface Guideline(iOS)を
Google翻訳に突っ込みながら読んでいくシリーズです。
- Apple Human Interface Guidelines(英語)
https://developer.apple.com/design/human-interface-guidelines/- ユーザインターフェイスのデザインのヒント(日本語)
https://developer.apple.com/jp/design/tips/【シリーズ記事まとめ】
Apple iOS Human Interface Guideline を読み込む【序章編】
Apple iOS Human Interface Guideline を読み込む【App Architecture編】←この記事Loading
コンテンツの読み込み中に、空白または静的な画面が表示されていると、アプリがフリーズしているように見え、混乱とフラストレーションが生じる結果、ユーザーがアプリを離れる可能性があります。
ロード発生を明確に伝える
少なくとも、何かが起こっていることを伝えるアクティビティスピナーを表示しましょう。
さらに良いのは、明示的な進捗状況を表示して、ユーザーが待機時間を推測できるようにすることです。できるだけ早くコンテンツを表示する
期待する画面が表示される前に、コンテンツの読み込みでユーザーを待たせないようにしましょう。
すぐに画面を表示し、プレースホルダーテキスト、グラフィックス、またはアニメーションを使用して、コンテンツがまだ利用できない場所を明らかにしましょう。
コンテンツが読み込まれると、これらのプレースホルダー要素を置き換えましょう。
可能な限り、アニメーションの再生中やユーザーがレベルやメニューをナビゲートしているときなど、今後のコンテンツをバックグラウンドでプリロードしましょう。ユーザーを学習させるまたは楽しませてローディング時間を隠す
ゲームプレイや面白いビデオシーケンス、興味深いプレースホルダーグラフィックスについてのヒントを示すことを検討しましょうロード画面をカスタマイズする
通常は標準的な進行状況インジケーターで問題ありませんが、場合によっては状況から外れていると感じられる場合があります。
アプリやゲームのスタイルに合ったカスタムアニメーションや要素を使用して、より没入感のある体験の設計を検討しましょう追加のガイダンスについては、Progress Indicatorsを参照してください。
Modality
モダリティは、ユーザーのそれまでのコンテキストから離れた一時的なモードでコンテンツを提示するデザイン手法であり、終了するには明示的なアクションが必要です。
モーダルでコンテンツを提示することにより、
- ユーザーは自己完結型のタスクまたは密接に関連する一連のオプションの操作に集中できます
- ユーザーに重要な情報を示し、必要に応じて行動させることができますiOSは、アプリの特定の状況で使用するアラート、アクティビティビュー(または共有シート)、およびアクションシートを提供しています。
アプリでカスタムモーダルコンテンツを表示するために、iOS 13以降では次の表示スタイルがサポートされています。Sheet
シートプレゼンテーションスタイルは、基礎となるコンテンツを部分的に覆うカードとして表示され、カバーされていないすべての領域は暗くすることで、ユーザーの操作を防ぎます。
親ビューまたは前のカードの上端は現在のカードの後ろに表示され、カードを開いたときに中断したタスクを思い出せるようにします。
人々は次の方法でカードを閉じることができます
- 画面の上部から下にスワイプする
- カードのコンテンツが一番上までスクロールされているときに画面上のどこからでも下にスワイプする
- ボタンをタップする
複雑なタスクを行わない、非没入型のモーダルコンテンツにはシートを使用します。
Fullscreen
全画面表示スタイルは、画面全体をカバーします。
前のビューは完全にカバーされているため、視覚的な混乱を最小限に抑えられます。
ユーザーはボタンをタップすることでフルスクリーンのモーダルビューを閉じます。フルスクリーンモーダルビューは、ビデオ、写真、カメラビューなどの没入型コンテンツ、ドキュメントのマークアップや写真の編集などの複雑なタスクといったフルスクリーンプレゼンテーションの恩恵を受ける場合に使用します。
注意
モーダルビュースタイルを使用して、スプリットビューペイン、ポップオーバー、またはフルスクリーンではない他のビュー内にモーダルコンテンツを表示する場合は、モーダルコンテンツを表示するときにシートの使用に切り替える必要があります。
合理的にモダリティを使用しましょう
モーダルエクスペリエンスを用いるのは、現在のタスクとは異なるタスクを選択したり実行したりすることにユーザーを集中させる必要がある場合のみです。
モーダルエクスペリエンスは、現在の状況からユーザーを引き離し、閉じるためのアクションを必要とするので、明確なメリットが得られる場合にのみ使用しましょう。不可欠な(かつ実利的な)情報を提供するためにアラートを残しておきましょう
通常、アラートは何かが間違っている場合に表示されます。
アラートはエクスペリエンスを中断し、閉じるにはタップが必要になるため、ユーザーにはアラート表示が正当であることを感じさせることが重要です。
ガイダンスについては、アラートを参照してください。モーダルタスクはシンプルで短く、焦点を絞りましょう
アプリ内でアプリを作成しないでください。
モーダルタスクが複雑すぎると、モーダルコンテキストに入ったときに中断したタスクを見失う可能性があります。
ビューの階層を含むモーダルタスクの作成には特に注意してください。
人々は操作に迷い、手順をさかのぼる方法を忘れる可能性があるためです。
モーダルタスクにサブビューを含める必要がある場合は、階層を通る単一のパスと完了までの明確なパスを指定します。
また、タスクを完了する以外の目的で完了ボタンを使用しないでください。常にモーダルビューを閉じるボタンを含めしょう
たとえば、完了またはキャンセルのボタンが使用できます。
ボタンを含めることで、モーダルビューに対してアクセシビリティ技術を適用でき、ジェスチャーの代替手段を提供できます。必要に応じて、モーダルビューを閉じる前に確認をして、データの損失を回避しましょう
ユーザーが表示を閉じるためにジェスチャまたはボタンのいずれかを使用するか関わらず、アクションによってユーザーが生成したコンテンツが失われる可能性がある場合は、状況を説明し解決方法を提供するアクションシートを提示しましょう。ポップオーバーの上にカードを表示しない
ポップオーバー内にカードを表示することはできますが、ポップオーバーの上には何も表示するべきではありません。(アラートの場合は除く)
ポップオーバーでユーザーがアクションを起こした後にカードを表示する必要がある場合は、その前にポップオーバーを閉じてください。一般に、モーダルタスクを識別するタイトルを表示しましょう
人々がモーダルタスクに入ると、以前のコンテキストから切り替わるので、新しいコンテキストを明確に示すことをお勧めします。
タスクをより完全に説明したり、ガイダンス用のテキストをビューの他の部分に置くことも可能です。モーダルビューの外観をアプリと調整しましょう
たとえば、モーダルビューにナビゲーションバーが含まれる場合、アプリのナビゲーションバーと同じ外観を使用する必要があります。アプリ内で意味のあるモーダルの遷移スタイルを選択しましょう
アプリと調整して、モーダルによる一時的なコンテキストの転換の認識を高められるな遷移スタイルを使用しましょう。
デフォルトのトランジションでは、モーダルビューが画面の下から上に垂直にスライドし、閉じられたときに元に戻ります。
アプリ全体で一貫したモーダルの遷移スタイルを使用しましょう。開発者向けのガイダンスについては、UIViewControllerおよびUIPresentationControllerを参照してください。
Navigation
ユーザーは、自分の期待にそぐわない挙動が起きない限り、アプリのナビゲーションには気付きません。
開発者のなすべきことは、アプリ自体に注意を向けさせることなく、アプリの構造と目的をサポートできる方法でナビゲーションを実装することです。
ナビゲーションは自然で馴染みのあるものである必要があり、インターフェイスを支配したり、コンテンツへの集中を奪ったりするべきではありません。
iOSには、3つの主なナビゲーションスタイルがあります。Hierarchical Navigation(階層ナビゲーション)
目的の画面に到達するまで、画面ごとに1つの選択を行います。
別の目的画面に行くには、ステップを戻るか、最初からやり直すことで、別の選択を行う必要があります。
設定とメールではこのナビゲーションスタイルが使用されています。Flat Navigation(フラットナビゲーション)
複数のコンテンツカテゴリを切り替えます。
音楽とApp Storeではこのナビゲーションスタイルが使用されています。Content-Driven or Experience-Driven Navigation(コンテンツ駆動型またはエクスペリエンス駆動型のナビゲーション)
コンテンツ内を自由に移動するか、コンテンツ自体がナビゲーションを定義します。
ゲーム、書籍、その他の没入型アプリには通常、このナビゲーションスタイルを使用します。一部のアプリは、複数のナビゲーションスタイルを組み合わせることもできます。
たとえば、フラットナビゲーションを使用するアプリでは、各カテゴリ内で階層ナビゲーションが実装できます。
常に明確なパスを提供しましょう
ユーザーにはアプリ内のどこにいるのか、そして次の目的に進む方法を常に知らせる必要があります。
ナビゲーションのスタイルに関係なく、コンテンツのパスは論理的かつ予測可能で、追跡しやすいことが不可欠です。
一般に、ユーザーにはそれぞれの画面へのパスを1つだけ示します。
複数のコンテキストから画面を表示する必要がある場合は、アクションシート、アラート、ポップオーバー、またはモーダルビューの使用を検討してください。
詳細については、アクションシート、アラート、ポップオーバー、およびモダリティを参照してください。コンテンツにすばやく簡単にアクセスできるような情報構造を設計しましょう
タップ、スワイプ、および画面の数を最小限にして情報構造をまとめましょう。タッチジェスチャを使用して、流動性を作成しましょう
最小限の動きでインターフェイスを簡単に移動できるようにしましょう。
たとえば、画面を横へスワイプして前の画面に戻るようにします。標準のナビゲーションコンポーネントを使用しましょう
可能な限り、ページコントロール、タブバー、セグメンテッドコントロール、テーブルビュー、コレクションビュー、スプリットビューなどの標準ナビゲーションコントロールを使用してください。
ユーザーはすでにこれらのコントロールに慣れ親しんでおり、アプリ内を移動する方法を直感的に知っています。ナビゲーションバーを使用して、階層を移動させましょう
ナビゲーションバーのタイトルには、階層内の現在の位置を表示できます。
戻るボタンを使用すると、前の画面に簡単に戻ることができます。
具体的なガイダンスについては、ナビゲーションバーを参照してください。タブバーを使用して、コンテンツまたは機能の同列なカテゴリを提示しましょう
タブバーを使用すると、アプリ内での現在の位置に関係なく、すばやく簡単にカテゴリを切り替えることができます。
具体的なガイダンスについては、タブバーを参照してください。同タイプコンテンツのページが複数ある場合は、ページコントロールを使用しましょう
ページコントロールは、使用可能なページ数と現在アクティブなページを明確に伝えます。
天気アプリではページコントロールを使用して、場所固有の天気ページを表示しています。
具体的なガイダンスについては、ページコントロールを参照してください。ヒント
セグメンテッドコントロールとツールバーではナビゲーションはできません。
情報をさまざまなカテゴリに整理するために、セグメンテッドコントロールを使用します。
現在のコンテキストを操作するためのコントロールを提供するために、ツールバーを使用します。
これらのタイプの要素の詳細については、各ページを参照してください。
セグメンテッドコントロール
ツールバー
Onboarding
起動時間は、新しいユーザーをオンボーディングし、リピーターとは再会できる最初の機会です。
高速で楽しく、教育的なアプリ起動体験を設計しましょう。ここで扱われているユーザーオンボーディングについてはこちらの記事も参考になりました!
「サービス・アプリのユーザー定着率UP!ファンを増やすための仕組み「ユーザーオンボーディング」とは?」
起動画面をつくりましょう
アプリの起動時に起動画面が表示されることで、アプリが高速で応答性が高いという印象をユーザーに与えながら、初期コンテンツを読み込むことができます。
この画面は、アプリの最初の画面にすぐに置き換えられるため、ローカライズ可能なテキストとインタラクティブな要素を除いて、その画面によく似ているはずです。
詳細については、起動画面を参照してください。適切な向きで起動しましょう
アプリがポートレートモードとランドスケープモードの両方をサポートしている場合、デバイスの現在の向きに対応して起動する必要があります。
1つの向きでのみ使用するアプリの場合、常にその方向で起動し、必要に応じてユーザーがデバイスを回転できるようにする必要があります。
説得力のある理由がない限り、横向きモードのアプリは、デバイスが左に回転したか右に回転したかに関係なく、正しい方向を向く必要があります。
追加のガイダンスについては、適応性とレイアウトを参照してください。すぐにアプリ使用に移りましょう
コンテンツに到達し、アプリの使い始めるのに時間がかかるようなスプラッシュ画面、メニュー、指示を表示しないこと。
代わりに、ユーザーがすぐにアプリに没入できるようにします。
アプリにチュートリアルやイントロシーケンスが必要な場合は、スキップする方法を提供し、リピートユーザーには表示しないようにします。ヘルプの必要性を予測しましょう
ユーザーがアプリ使用において行き詰まる可能性がある場面を積極的に探しましょう。
たとえば、ゲームでは、一時停止したときやキャラクターを操作していないときに、気軽に役立つヒントを表示できます。
見逃した場合に備えて、ユーザーがチュートリアルを再生できるようにしましょう。チュートリアルの基本事項を順守しましょう
初心者にガイダンスを提供することは問題ありませんが、ユーザーをアプリ使用のために教育することは優れたアプリ設計の代替にはなりえません。
何よりもまず、アプリ操作を直感的にしましょう。
あまりにも多くのガイダンスが必要な場合、アプリの設計を再検討しましょう。学習は楽しく、発見可能に
動かしながら学ぶことは、説明書を読みながらよりもはるかに楽しく、ユーザーに対して効果的です。
アニメーションとインタラクティビティを使用し、コンテキストに沿って徐々に身に付けさせましょう。
また、インタラクティブに見えるスクリーンショットは表示しないでください。表立ってデバイスのセットアップ情報を尋ねることは避ける
ユーザーはアプリがすぐに機能することを期待しています。
大多数に合わせてアプリケーションを設計し、異なる構成を必要とする少数のユーザーには、ニーズに合わせて設定を調整できるようにしましょう。
可能な限り、デバイスの設定やデフォルト値、またはiCloudなどの同期サービスを介してセットアップ情報を取得しましょう。
セットアップ情報を要求する必要がある場合は、アプリ内で最初にそれを要求し、ユーザーがアプリの設定で後で変更できるようにします。アプリ内でライセンス契約および免責事項を表示しない
アプリをダウンロードする前、App Storeで同意書と免責事項を表示してください。
これらの項目をアプリに含める必要がある場合は、ユーザーエクスペリエンスを妨げないバランスの取れた方法で組み込みましょう。アプリの再起動時に以前の状態を復元しましょう
前回使用時の状態にするために、ユーザーに操作をさかのぼらないでください。
アプリの状態を保持および復元して、中断したところから続行できるようにしましょう。アプリ評価を早期にまた、頻繁に求めすぎないこと
あまりにも早期段階または頻繁に評価を求められるとユーザーは面倒になり、開発者が受け取れる有益なフィードバックの量も減ってしまいます。
よく考えられたフィードバックを得るには、評価を求める前に、アプリに対する意見を作れる時間をユーザーに与えましょう。
アプリ評価を決定する方法は常に提供しておきましょう。
ユーザーにアプリの評価を強制することはしないこと。再起動を奨励しないこと
再起動には時間がかかる上に、あなたのアプリは信頼性が低く、使いにくいように思われます。
システム起動直後でないと実行できないメモリ使用やその他の問題がアプリにある場合、それらの問題に対処する必要があります。Requesting Permission
ユーザーには、アプリが現在の場所、カレンダー、連絡先情報、リマインダー、写真などの個人情報にアクセスできるかを決める許可を与える必要があります。
この情報にアクセスできるアプリを使用することの利便性は高く評価されていますが、ユーザーが自身で個人データを管理できることも求められています。
たとえば、人々は写真に物理的な位置を自動的にタグ付けしたり、近くの友人を見つけたりできる機能を気に入りますが、そのような機能を無効にするオプションも必要です。
- アプリで明らかに必要な場合にのみ、個人データを要求します
個人情報の要求に対してユーザーが疑念を持つのは自然なことです。 特に明白な必要性がない場合は当然です。 個人データが明らかに必要な機能を使用する場合にのみ、許可の要求を行うようにしてください。 たとえば、位置追跡機能を有効にした場合、アプリは現在の場所へのアクセスのみを要求するようにします。
- アプリがその情報を必要とする理由を明記します
purpose stringまたはusage descriptioin stringとしてシステムの許可要求アラートに表示するカスタムテキストを提供し、また使用例を含めましょう。 テキストは短く具体的にし、大文字と小文字を使い分け、ユーザーにプレッシャーを与えないよう丁寧にしましょう。 また、システムは既にアプリを識別しているので、アプリ名を含める必要はありません。 開発者向けのガイダンスについては、ユーザーのプライバシーの保護を参照してください。
Example purpose strings OK The app records you during the night to detect snoring sounds. NG Microphone access needed for a better experience. NG Turn on microphone access.
アプリの機能に必要な場合にのみ、起動時に許可をリクエストしましょう
アプリの動作が個人情報に依存していることが明らかな場合、ユーザーは個人情報の要求を煩わしくは思いません。不必要に位置情報を要求しないこと
位置情報にアクセスする前に、システムをチェックして、位置情報サービスが有効になっているかどうかを確認してください。
この知識があれば、機能で実際に位置情報が必要になるまでアラートを遅らせるか、アラートを完全に回避することができます。
位置情報機能の実装方法については、MapKitおよびLocation and Maps Programming Guideを参照してください。システム提供のアラートを使用しましょう
標準のアクセス許可アラートのテキストをカスタマイズできるので、標準のアラートの動作や外観を複製したカスタムプロンプトは追加しないでください。Settings
一部のアプリでは、セットアップまたは設定を選択する方法を提供する必要がありますが、ほとんどのアプリはそれらは回避または遅延できます。
成功したアプリは、ほとんどのユーザーにとってすぐによく機能しますが、エクスペリエンスを調整する便利な方法も提供しています。
多くのユーザーが期待する通りに機能するようにアプリを設計すると、設定の必要を減らせます。
システムからできることを考えましょう
ユーザー、デバイス、または環境に関する情報が必要な場合は、ユーザーに尋ねるのではなく、可能な限りシステムに問い合わせてください。
たとえば、郵便番号の入力を求めてローカルオプションを提示する代わりに、現在地を利用する許可を求めましょう。
ユーザーが情報へのアクセスを拒否した場合は、優雅に手動入力に切り替えましょう。アプリ内での設定オプションには慎重に優先順位を付けましょう
アプリのメイン画面は、重要なオプションや頻繁に変更されるオプションに適しています。
セカンダリ画面は、たまにしか変更されないオプションに適しています。頻繁に変更されない設定オプションは「設定」アプリに配置しましょう
「設定」アプリは、システム全体で構成を変更するための中心的な場所ですが、ユーザーがアプリを離れる必要があります。
アプリ内で直接設定を調整する方がはるかに便利です。
めったに変更を必要としない設定を提供しなければならない場合は、開発者向けのガイダンスについては、Preferences and Settings Programming Guideの中の Implementing an iOS Settings Bundleを参照してください
- 必要に応じて「設定」アプリへのショートカットを提供しましょう
「設定に移動> MyApp>プライバシー>位置情報サービス」など、ユーザーを設定に導くテキストがアプリに含まれている場合、その場所を自動的に開くボタンを提供しましょう。 開発者向けのガイダンスについては、UIApplicationの中のopenSettingsURLStringを参照してください。
- 投稿日:2019-09-29T04:29:19+09:00
Apple iOS Human Interface Guideline を読み込む【序章編】
TL;DR
AppleのHuman Interface Guideline(iOS)を
Google翻訳に突っ込みながら読んでいくシリーズです。
- Apple Human Interface Guidelines(英語)
https://developer.apple.com/design/human-interface-guidelines/- ユーザインターフェイスのデザインのヒント(日本語)
https://developer.apple.com/jp/design/tips/【シリーズ記事まとめ】
Apple iOS Human Interface Guideline を読み込む【序章編】←この記事
Apple iOS Human Interface Guideline を読み込む【App Architecture編】Themes(テーマ)
iOSのデザインテーマ
アプリデザイナーとして、App Storeチャートのトップに浮上する並外れた製品を提供するチャンスがあります。
そのためには、品質と機能に対する高い期待に応える必要があります。iOSを他のプラットフォームと区別する3つの主要なテーマがあります。
明快さ
システム全体で、テキストはどのサイズでも読みやすく、アイコン(が表す意味)は正確かつ明快で、装飾はかすかで適切にし、機能性に焦点を絞ることがデザインの動機となっています。
好ましくないスペース、色、フォント、グラフィックス、その他のインターフェイス要素は、重要なコンテンツを微妙に強調してしまい、相互関係を示唆します。敬意
滑らかな動きと鮮明で美しいインターフェースにより、人々はコンテンツと競合することなくコンテンツを理解し、対話することができます。
通常、コンテンツは画面全体に表示されますが、半透明性とぼかしが多くの場合より多くをほのめかします。
ベゼル、グラデーション、ドロップシャドウの使用を最小限に抑えることで、コンテンツを最重要視しながら、インターフェイスを明るく風通しの良い状態に保てます。深度
明確な視覚層と現実的な動きがアプリ内の階層を伝え、活力を与え、理解を促進します。
触れて発見することで喜びが高まり、コンテキストを失うことなく機能や追加コンテンツにアクセスできるようになります。
トランジションは、コンテンツ内をナビゲートするときに階層の意識を示します。設計原理
インパクトとリーチを最大化するためには、アプリのアイデンティティを想像する際に次の原則に留意してください。
美的整合性
美的整合性とは、アプリの外観と動作がその機能とどの程度うまく統合されているかを表します。
たとえば、重要なタスクの実行を支援するアプリでは、繊細で控えめなグラフィックス、標準的なコントロール、予測可能な動作を使用することで、アプリの使用に集中させ続けることができます。
一方、ゲームなどの没入型アプリでは、発見を促し、その中で楽しさと興奮を約束するような魅力的な外観が求められます。一貫性
一貫性のあるアプリは、システムが提供するインターフェイス要素、よく知られているアイコン、標準のテキストスタイル、統一された用語を使用することで、ユーザーがよく知る規格と枠組みを実装します。
そのようなアプリには、ユーザーが想定する方法での機能と動作が組み込まれています。直接操作
画面上のコンテンツを直接操作することで、ユーザーを引き付け、理解を促進します。
ユーザーは、デバイスを回転させたり、ジェスチャを使用して画面上のコンテンツに作用を与えた時に、直接操作を経験します。
直接操作を通して、ユーザーは自身の行いによる結果を即座に目に見える形で知ることができます。フィードバック
フィードバックはアクションを受け取り、結果を表示することで人々に情報を提供します。
組み込みのiOSアプリは、すべてのユーザーアクションに応じて知覚可能なフィードバックを提供します。
インタラクティブな要素はタップすると簡単に強調表示され、プログレスインジケータは長時間の動作の状況を伝え、アニメーションとサウンドはアクションの結果を明確にしてくれます。メタファー
アプリの仮想オブジェクトとアクションが身近な体験のメタファーである場合、その体験が実世界由来であるか、デジタル世界由来であるかに関わらず、ユーザーはより迅速にアプリの使用方法を学習します。
人々は物理的に画面を操作しているので、メタファーはiOSにおいてうまく機能します。
ユーザーはビューを移動させて、下にあるコンテンツを、見ることができます。
コンテンツをドラッグやスワイプすることができます。
スイッチの切り替え、スライダーの移動や、ピッカーをスクロールすることもできます。
彼らは本や雑誌のページをめくることも可能です。ユーザーをコントロールする
iOSでは、アプリではなく人がコントロールされます。
アプリは一連のアクションを提案したり、危険な結果について警告したりできますが、通常はアプリが意思決定を引き継ぐのは間違いです。
最高のアプリでは、ユーザーが可能な操作と、望ましくない結果を回避することの間の正しいバランスが存在します。
アプリは、インタラクティブな要素を使い慣れた予測可能なものにしておき、破壊的なアクションには確認をし、すでに進行中であっても操作を簡単にキャンセルできるようにすることで、あたかもユーザーがアプリをコントロールしているように感じさせることができます。iPad Apps for Mac
iPadOSも出てきたということで割愛させていただきます...
HIGにも近いうちにiPadOS版が追加されるのかな。Interface Essentials(インターフェースの基本)
ほとんどのiOSアプリは、共通のインターフェイス要素が定義されたプログラミングフレームワークであるUIKitのコンポーネントを使用して構築されます。
このUIKitを使うことで、アプリにシステム全体で一貫した外観を実現すると同時に、高度なカスタマイズを提供することができます。
UIKitの要素は柔軟で使いやすいです。
これらは適応性があり、どのiOSデバイスでも見栄えの良い単一のアプリを設計でき、システムが外観の変更を導入すると自動的にアップデートされます。
UIKitが提供するインターフェース要素は、次の3つの主要なカテゴリーに適合します。
Bars
アプリのどこにいるのかを伝え、ナビゲーションを提供します。
アクションの開始や情報を伝えるためのボタン、その他の要素を含めることもできます。Views
テキスト、グラフィックス、アニメーション、インタラクティブな要素など、ユーザーがアプリで見る主要なコンテンツが含まれています。
ビューに対しては、スクロール、挿入、削除、配置などの動作が可能です。Controls
アクションを開始し、情報を伝えます。
ボタン、スイッチ、テキストフィールド、およびプログレスインジケーターは、コントロールの例です。iOSのインターフェースの定義に加えて、UIKitはアプリが採用できる機能を定義します。
たとえば、このフレームワークを通じて、アプリはタッチスクリーン上のジェスチャーに応答し、描画、アクセシビリティ、印刷などの機能を有効にできます。
iOSは、Apple Pay、HealthKit、ResearchKitなどの他のプログラミングフレームワークやテクノロジーとも緊密に統合されているため、驚くほど強力なアプリを設計できます。