UX設計素人が学んでみて感じた、UX設計技法のよさみとその導入のつらみ
↑こちらへ移行しました。
エンジニアリング組織論への招待を読んでよかったこと
エンジニアリング組織論への招待 ~不確実性に向き合う思考と組織のリファクタリング
- 作者: 広木大地
- 出版社/メーカー: 技術評論社
- 発売日: 2018/02/22
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
小学生並みのタイトルだ...
※書評ではありません。
※しかもまだ読んでいる途中です。
「悩んでいる」状態から「考える」行為へと遷移しやすくなった
この本では、「不安」を生み出す「わからないこと」(=不確実性)を以下のように分けてるわけですが、
- 未来=環境不確実性
- 方法不確実性
- 目的不確実性
- 他人=通信不確実性
この「なんちゃら不確実性」っていう言葉を覚えたことでいいことがありました。
自分は普段わりと「悩んでいる」状態になりがちです。
「なんかな〜 う〜ん なんだろな〜 あれかな〜 これかな〜 いやでもな〜」
みたいに、何も答えを出さず延々と時間だけ使い、ふと我に返って
「オレはこの1時間一体何を...」ってなる感じです。
で、この悩んでるときって、きっと何かしらの不確実性を抱えてるんだと思いました。
「なんちゃら不確実性」を覚えてからは、悩み始めると
「オレは今どの不確実性で困ってるんだ?」と考えることができるようになりました。
そうすると、
「どういうゴールを目指せばいいかよくわかってないから目的不確実性だな。じゃあゴールを明確にするにはどうすればいいだろう?」って考えたり、
「ゴールはわかってるんだがどうやってそこにたどり着けばいいのかわかってないから方法不確実性だ。じゃあ、たどり着き方にはどういう方法があるだろう?いくつか案を出してみよう」みたいに考えることが出来るようになって、
無駄に悩んで時間を使うことが減ったように思います。
こんなことは、当たり前に出来ている人は出来ていることだと思います。
でも自分みたいに悩んでいる状態になりがちな人間にとっては、
「名前をつけることでその存在に気づきやすくなる」この感じがとてもありがたかったです。
boundingRectWithSize:options:attributes:context: で計算した値を元に View の size を設定すると文字が切れる問題
問題
boundingRectWithSize:options:attributes:context で計算した値を
そのまま UILabe やら UITextView の size の値に設定すると、文字が切れてしまう。
解決法
ドキュメントに書いてあるまんまなのですが、
This method returns fractional sizes (in the size component of the returned CGRect); to use a returned size to size views, you must use raise its value to the nearest higher integer using the ceil function.
ということで、ceil() してあげましょう、とのこと。
レイアウトの階層を表示
Android だとレイアウトの階層構造が見れる hierarchy view perspective があるんですが、
Xcode だとそういうのがパッと見みつからなかったのでどうやってレイアウトの状態を把握するのだろうと思っていたところ、教えてもらったのでメモ。
UIView に対して recursiveDescription を呼び出す
通常のメソッド呼び出しでは呼べないので performSelector する。
UIViewController の view の階層を見るときは以下の様な感じ。
NSLog(@"%@", [self.view performSelector:@selector(recursiveDescription)]);
出力は以下の様な感じ。
2013-08-17 21:04:53.358 UIViewControllerTest[60940:11303] <UIView: 0x76935f0; frame = (0 20; 320 460); autoresize = W+H; layer = <CALayer: 0x7693650>> | <UIRoundedRectButton: 0x7692130; frame = (25 397; 63 44); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7691940>> | | <UIGroupTableViewCellBackground: 0x7692200; frame = (0 0; 63 44); userInteractionEnabled = NO; layer = <CALayer: 0x7691970>> | | <UIImageView: 0x76929d0; frame = (1 1; 61 43); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7692380>> - (null) | | <UIButtonLabel: 0x7692480; frame = (12 12; 39 19); text = 'show'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7692520>> | <UIView: 0x76932f0; frame = (0 0; 320 224); clipsToBounds = YES; autoresize = LM+RM; layer = <CALayer: 0x7693290>> | <UIRoundedRectButton: 0x768df00; frame = (232 412; 55 44); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x768e020>> | | <UIGroupTableViewCellBackground: 0x768e800; frame = (0 0; 55 44); userInteractionEnabled = NO; layer = <CALayer: 0x768e8d0>> | | <UIImageView: 0x768f940; frame = (1 1; 53 43); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7690050>> - (null) | | <UIButtonLabel: 0x768f520; frame = (12 12; 31 19); text = 'hide'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x768f610>> | <UIRoundedRectButton: 0x7692710; frame = (229 361; 71 44); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x76927e0>> | | <UIGroupTableViewCellBackground: 0x7692810; frame = (0 0; 71 44); userInteractionEnabled = NO; layer = <CALayer: 0x7692890>> | | <UIImageView: 0x7693770; frame = (1 1; 69 43); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7693e90>> - (null) | | <UIButtonLabel: 0x7693fa0; frame = (12 12; 47 19); text = 'switch'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7694040>> | <UIRoundedRectButton: 0x7691190; frame = (96 412; 129 44); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7691260>> | | <UIGroupTableViewCellBackground: 0x7691290; frame = (0 0; 129 44); userInteractionEnabled = NO; layer = <CALayer: 0x7691310>> | | <UIImageView: 0x7691380; frame = (1 1; 127 43); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x76913e0>> - (null) | | <UIButtonLabel: 0x7691540; frame = (12 12; 104 19); text = 'view hierarchy'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7691630>>
ログ出力なのであまり見やすくはないけど、何もないよりはマシですね。
View のない non-UI ( non-graphical ) fragment の1つの使い道
fragment に関する公式ドキュメントを読んでると、UI を持たない fragment を作ることもできるとあるけれど、一体何に使うのかよくわかりませんでした。
ところが最近、会社の先輩からその1つの使い道を教えてもらったので、忘れないようにメモ。
DialogFragment の Helper クラスとして使う
たとえば、以下の様な要件があるときに、non-UI fragment が使えます。
- ネット上の画像を取得して、DialogFragment の layout にその画像を表示させたい
- Activity に LoaderCallbacks を implements させたくない
このとき、以下のように素直に DialogFragment を継承したクラスに LoaderCallbacks を implements させて AsyncTaskLoader を呼びだそうとすると、
「IllegalStateException: Fragment not attached to Activity」が発生します。
public class SampleDialogFragment extends DialogFragment implements LoaderCallbacks<Bitmap>{ private FragmentManager mManager; private String mTag; private Bitmap mImageData; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View dialogView = inflater.inflate(R.layout.dialog_fragment_layout, container, false); ImageView imageView = (ImageView) dialogView.findViewById( R.id.image ); imageView.setImageBitmap( mImageData ); return dialogView; } // Activity からはこのメソッドを呼び出す public void showDialog ( FragmentManager manager, String tag ) { mManager = manager; mTag = tag; getLoaderManager().initLoader(0, null, this ); } @Override public Loader<Bitmap> onCreateLoader(int arg0, Bundle arg1) { return new SomeAsyncTaskLoader( getActivity() ); } @Override public void onLoadFinished(Loader<Bitmap> arg0, Bitmap imageData) { mImageData = imageData; show( mManager, mTag ); } @Override public void onLoaderReset(Loader<Bitmap> arg0) {} }
LoaderManager は Activity/Fragment のライフサイクルと連動して動くみたいなんですが、
上記の showDialog () を呼んだ時点ではまだこの fragment 自体が activity に紐付いておらず、activity のライフサイクルと連動していないので、結果としてLoaderManager が連動するライフサイクルが定まらないためにこの Exception が投げられるのかなと思います。
これを回避するために、UI を持たないフラグメントをただのインタフェースとして用意し、Activity にそのフラグメントを追加し、Activity からはそのフラグメントが提供するメソッドを介して DialogFragment を表示させるようにします。
おおまかな手順は、
- non-UI fragment を用意し、LoaderCallbacks をこれに implements する。
- Activity で non-UI fragment を add する
- Activity から non-UI fragement が提供するメソッドが呼び出されたら、そのメソッド内で loader を起動する
- onLoadFinished() で 実際の DialogFragment を呼び出す
という感じです。
しかし、まだ問題があります。
Can not perform this action inside of onLoadFinished
たとえば素直に onLoadFinished() 内で DialogFramgnet のインスタンスを生成し、表示させようとします。
@Override public void onLoadFinished(Loader<Bitmap> arg0, Bitmap imageData) { SampleDialogFragment dialog = new SampleDialogFragment(); dialog.show( getFragmentManager(), TAG, imageData ); }
すると以下の様な exception が投げられます。
FATAL EXCEPTION: main
java.lang.IllegalStateException: Can not perform this action inside of onLoadFinished
これについては、以下の stack over flow あたりを参考に回避します。
android - This Handler class should be static or leaks might occur: IncomingHandler - Stack Overflow
もろもろまとめると
サンプルプロジェクトを以下に置いてみました。
kiyotakagoto/NonUIFragmentSample · GitHub
Android で View の Id が取得できない場合にコンテンツ表示領域のサイズを取る方法
コンテンツ表示領域の width, height を取得したい場合、通常なら
Activity#findViewById() して取得した ViewGroup の getter である getWidth, getHeight を使えばいい。
一方、たとえば SDK の開発などで、
”SDK組み込み先のアプリのコンテンツ表示領域の特定位置に何らかの View (fragmentなど)を表示させたい”
といったときは、view の id がわからず、この方法が使えないこともある。
そういうときは、以下のように、 root のレイアウトを取得する。
ViewGroup root = (ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content); root.getHeight(); root.getWidth();
参考
Activity#findViewById での取得
Android 端末の画面サイズを取得-Oboe吹きプログラマの黙示録
Androidの画面サイズを攻略して機種依存を吸収する(ナビゲーションバーとステータスバーのサイズを取得する) | Tech Booster
ViewGroup#getWindow からたどって取得
Activityのビュー階層とコンテンツルート(View)を取得する - Kazzzの日記
Androidアプリサービス開発者ブログ:レイアウトのRootのView取得方法