健康意識の高まり

いろんなメモなど

UX設計素人が学んでみて感じた、UX設計技法のよさみとその導入のつらみ

note.mu

↑こちらへ移行しました。

エンジニアリング組織論への招待を読んでよかったこと

小学生並みのタイトルだ...

※書評ではありません。
※しかもまだ読んでいる途中です。

「悩んでいる」状態から「考える」行為へと遷移しやすくなった

この本では、「不安」を生み出す「わからないこと」(=不確実性)を以下のように分けてるわけですが、

  • 未来=環境不確実性
    • 方法不確実性
    • 目的不確実性
  • 他人=通信不確実性

この「なんちゃら不確実性」っていう言葉を覚えたことでいいことがありました。

自分は普段わりと「悩んでいる」状態になりがちです。
「なんかな〜 う〜ん なんだろな〜 あれかな〜 これかな〜 いやでもな〜」
みたいに、何も答えを出さず延々と時間だけ使い、ふと我に返って
「オレはこの1時間一体何を...」ってなる感じです。
で、この悩んでるときって、きっと何かしらの不確実性を抱えてるんだと思いました。

「なんちゃら不確実性」を覚えてからは、悩み始めると
「オレは今どの不確実性で困ってるんだ?」と考えることができるようになりました。
そうすると、
「どういうゴールを目指せばいいかよくわかってないから目的不確実性だな。じゃあゴールを明確にするにはどうすればいいだろう?」って考えたり、
「ゴールはわかってるんだがどうやってそこにたどり着けばいいのかわかってないから方法不確実性だ。じゃあ、たどり着き方にはどういう方法があるだろう?いくつか案を出してみよう」みたいに考えることが出来るようになって、
無駄に悩んで時間を使うことが減ったように思います。

f:id:kiyotakagoto:20180317205813p:plain こんなことは、当たり前に出来ている人は出来ていることだと思います。
でも自分みたいに悩んでいる状態になりがちな人間にとっては、
「名前をつけることでその存在に気づきやすくなる」この感じがとてもありがたかったです。

iTunes on Mac に音源ファイルを追加して、そいつらをプレイリストに入れて、かつ iOS に同期させる方法

意外とぐぐってもスッと出てこない気がしたので整理

ユースケース

  • Mac上にある mp3 とか m4a を iPhone で再生したいなー かつプレイリストに入れたいなー でもケーブルつないで同期とかめんどうなのでネットワーク経由でなんとかスッと出来ないかなー」というとき
    • フリーな音源を Mac 上に落としてきたときとかですかね
  • かつ普段は Apple Music / iCloud ミュージックライブラリを Mac 側も iPhone 側も使っている

確認環境

方法

  • iTunes on Mac を開く
  • Cmd+O または「ファイル -> ライブラリに追加」から、音声ファイルを選択、追加
  • 各音声ファイルを「iCloud ミュージックライブラリに追加」する
    • 「・・・」ボタンないし2本指タップから
    • 「最近追加した項目」ないし「曲」で検索かけると楽
    • 複数選択していっぺんにアップロード可能
  • プレイリストに追加する

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();