Rodhos Soft

備忘録を兼ねた技術的なメモです。Rofhos SoftではiOSアプリ開発を中心としてAndroid, Webサービス等の開発を承っております。まずはご相談下さい。

レイアウト関連

  1. Drawableフォルダには画材的なxmlを置く。
  2. dimensフォルダにはデバイスの大きさごとの指定を置く。
  3. FrameLayoutは何もしないレイアウト
  4. RelativeLayoutは何々は何々の上にあるとかを指定するレイアウト
  5. 絶対座標は使わないこと。
  6. データバインディングデータ バインディング ライブラリ | Android Developers

バインドの取得とセット

		binding = DataBindingUtil.bind(mView);
		binding.setHoge(mHoge);

Hogeのところはxmlのdataのnameによって自動的に作られる。bindingの型もdataに設定するtypeから作られる。

xmlはレイアウトを以下で包む必要がある。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <import type="android.view.View"/>
        <variable name="hoge" type="jp.co.hoge.app.activity.hogeActivity.HogeItem" />
    </data>

バインドの指定は

    <TextView
        android:text='@{hoge.name, default=名前 }'
        />

エミュレータのプロキシ認証

やったこと。

エミュレータの設定でプロキシ設定する

うまくいかなかったのでコマンドライン上でabd, emulatorを使うことにした。

まずパスを通した。

export PATH=/Applications/adt-bundle-mac-x86_64***/sdk/platform-tools:$PATH
export PATH=/Applications/adt-bundle-mac-x86_64***/sdk/tools:$PATH

使い方は

emulator -list-avds

で機種一覧がみれる。

プロキシ経由での実行は

emulator -avd devicename -http-proxy http://username:password@proxyhost:port

abdの使い方は以下に詳しい。
qiita.com

googleのブラウザで認証を求められて入れたら検索できるようになった。
しかし、自分のアプリではproxyでエラーになっている。

アプリをいじる

HTTPクライアントにプロキシ認証を仮にできるようにした。

OkHttpClientを使っているのでリクエストを投げる際に以下のように追加した。

       Authenticator authenticator = 						new Authenticator() {

            @Override
            public Request authenticate(Route route, Response response) throws IOException {
                String credentioal = Credentials.basic(username, password);
                return response.request().newBuilder().header("Proxy-Authorization", credentioal).build();
            }
        };

  Proxy proxy = new Proxy(Proxy.Type.HTTP,
                new InetSocketAddress(proxyhost,port)
        );

		OkHttpClient client = new OkHttpClient().newBuilder()
                .proxy(proxy)
                .authenticator(authenticator)
				.proxyAuthenticator(authenticator)
				.build();

これでプロキシをさしあたり通すことができた。

OKHTTPの使い方は
qiita.com

幾つかのメモ

アダプター

アイテムからアイテム用のビューへの変換、動的に行われる。getView
convertViewはリサイクルされてきたビューのこと。

データバインディング

DataBindingUtil.inflateでbindingを取得して、そいつにデータをセット、
データはレイアウトのほうで受け取れるようにしておく。


gradleで

  dataBinding {
        enabled = true
    }

レイアウトは

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="hoge" type="com.example.Hoge"/>
   </data>
   <LinearLayout
        android:id="@+id/activity_main"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{hoge.text1}"/>
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{hoge.text2}"/>
    </LinearLayout>
</layout>

みたいにする。

レイアウトを使う。

LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

qiita.com

GSon

JSONからモデルへ一括変換、モデルはアノテーションで何を受け取るか指定する。

Serializable

    @SerializedName("hoge")
    public String hoge;

ビューのタグ

クラスを一時的にもっておける

レイアウトのinclude

レイアウトを入れ子に。しかし共通化か冗長性か。

ハンドラーにランナブルをなげる。

	Handler handler = new Handler(getMainLooper());
	handler.post(new Runnable() {

AsyncTaskは簡単バージョン

SimpleDateFormat

日時のフォーマット

CoordinatorLayout

iOSのAutoLayoutに近い

画面遷移カスタマイズ2 viewの遷移

ビューの遷移を真似たい場合snapshotViewとtransitionをくみあわせる。

            let currentView = self.view.snapshotView(afterScreenUpdates: true)!
            self.view.addSubview(currentView)
            setup()
            UIView.transition(with: currentView, duration: 0.5, options: [.transitionFlipFromRight], animations: {
                currentView.frame = CGRect(origin: CGPoint(x:-self.view.frame.width, y:currentView.frame.origin.y), size: currentView.frame.size)
            } , completion: { _ in
                    currentView.removeFromSuperview()
            })

画面遷移カスタマイズ

class PushAnimator : NSObject, UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 1.0
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromV = transitionContext.view(forKey: .from) else {
            return
        }
        guard let toV = transitionContext.view(forKey: .to) else {
            return
        }
        
        let container = transitionContext.containerView
        container.insertSubview(toV, belowSubview: fromV)
        
        UIView.animate(withDuration: self.transitionDuration(using: transitionContext),
                       animations: {
                        fromV.alpha = 0.0
        },
                       completion: { finished in
                        fromV.alpha = 1.0
                        toV.alpha = 1.0
                        transitionContext.completeTransition(finished)
        })
        
    }
}

とやって、ナビゲーションのデリゲートに

    // 画面遷移
    func navigationController(_ navigationController: UINavigationController,
                              animationControllerFor operation: UINavigationControllerOperation,
                              from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        let animator = PushAnimator()
        switch operation {
        case .pop,.push:
            return animator
        case .none:
            return nil
        }
    }

UIViewのレイアウトライフサイクル

  1. 制約更新 updateConstraintsIfNeeded() → updateConstraints()が呼ばれる。
  2. フレーム更新 layoutIfNeeded() → layoutSubViews()が呼ばれる。 端末回転によるフレーム変更、UIScrollViewでcontentOffsetの変更時等
  3. レンダリング setNeedsDisplay() → drawRect()が呼ばれる。

UIViewControllerのレイアウトライフサイクル

  1. viewWillLayoutSubviews
  2. viewDidLyoutSubViews

AutoLayoutの制約式

基本は

A = aB + b

連立方程式を解く。

Alignment Rectangle (外接矩形)

装飾(影、角丸等)を除いたビュー。AutoLayoutはフレームではなくAlignment Rectangleを用いている。

Alignment Rectangleの表示方法

Edit Scheme のRunのArgumentで起動オプションとして設定できる。

制約定義 NSLayoutConstraint

+ (instancetype)constraintWithItem:(id)view1 // 対象A first itemと呼ばれる。
 attribute:(NSLayoutAttribute)attr1 // 対象Aに制約を追加する位置 Left,Right,Top,Bottom,Leading等
 relatedBy:(NSLayoutRelation)relation // ビュー間の関係 =,>=,<=
 toItem:(nullable id)view2  //  対象B オプショナル, second itemと呼ばれる。
attribute:(NSLayoutAttribute)attr2 // 対象Bに制約を追加する位置 Left等
 multiplier:(CGFloat)multiplier // 乗数 a
constant:(CGFloat)c; // 加える定数 b

制約式を書くと

 firstItem.attribute = a * secondItem.attribute + b

NSLayoutAttributte

typedef NS_ENUM(NSInteger, NSLayoutAttribute) {
    NSLayoutAttributeLeft = 1,
    NSLayoutAttributeRight,
    NSLayoutAttributeTop,
    NSLayoutAttributeBottom,
    NSLayoutAttributeLeading, // 先頭
    NSLayoutAttributeTrailing, // 末尾
    NSLayoutAttributeWidth,
    NSLayoutAttributeHeight,
    NSLayoutAttributeCenterX,
    NSLayoutAttributeCenterY,
    NSLayoutAttributeLastBaseline,
    NSLayoutAttributeBaseline NS_SWIFT_UNAVAILABLE("Use 'lastBaseline' instead") = NSLayoutAttributeLastBaseline, // テキストのベースライン
    NSLayoutAttributeFirstBaseline NS_ENUM_AVAILABLE_IOS(8_0), // 複数行テキストの最初の行のベースライン
    
    
    NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0), // 左のマージン
    NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0), // 右のマージン
    NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0), // 上のマージン
    NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0), // 下のマージン
    NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),// オブジェクトの左と右のマージンの間のx軸に沿った中心。 UIViewオブジェクトの場合、マージンはlayoutMarginsプロパティで定義される。
    NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
    
    NSLayoutAttributeNotAnAttribute = 0
};

Intrinsic Content Size

UILabel, UIButton, UIImageでコンテンツを圧縮、切り出しせずに表示するミニマムなサイズ。UILabelではテキストの内容によって変わる。
intrinsicContentSizeによって取得可能

Content Hugging Priority 大きくなりにくさ

縦横それぞれに指定可能。コンテンツに沿う優先度。Intrinsic Content Sizeにあわせてサイズが変化する。この優先度が低いと他の制約に従ってIntrinsic Content Sizeよりも大きくなる。

Content Compression Resistance Priority 小さくなりにくさ

前述の圧縮バージョン

Adaptive Layout

Adaptive Layout = レスポンシブレイアウト

iPhoneiPadという端末の区別はない。
どのようなウィンドウサイズでもレイアウトを維持する。


二つのポイント

1. AutoLayout
2. トレイトコレクション サイズクラスはトレイトコレクションに含まれる値の一つ

サイズクラス

iPad

  • Pro,Mini,Airは画面サイズが違うが同じ画面サイズを持っているものとして扱われる。
  • 縦向きでも横向きでもRegular-Regular

iPhone

  • 6系とそれ以前とで異なる。
  • 6系 縦 Regular-Compact , 横 Compact-Regular
  • それ以前 縦 Regular-Compact 横 Compact-Compact

商品の値段とNSNumberFormatter

SKProductの価格の加工などで使う。

    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    formatter.formatterBehavior = NSNumberFormatterBehavior10_4;
    formatter.numberStyle = NSNumberFormatterCurrencyStyle;
    formatter.locale = locale;
    NSString *string = [formatter stringFromNumber:price];

iPhone/iPadの画面

基本的には以下をみる

qiita.com

デザイン的には基本は横幅を気にする。
5sだと320
6だと375
6plusだと414
ipad 768
ipad pro 1024

縦はテーブル等で表示する場合はそこまで気にならない。ipadは回転を許容する場合は気にする必要がある。
5sだと 568
6だと 667
6plus 736
ipad 1024
ipad pro 1366

実際のピクセルRetinaとの兼ね合いになる。

FutureパターンをSwiftで書く

Swiftで

まずリクエストプロトコルを作る。

public protocol Request {
    func getResult()->String    
}

それを実装するFurureを作る。

public class Future:Request {
    private let semaphore:DispatchSemaphore = DispatchSemaphore(value: 0)
    private var ready = false
    private var result:Result? = nil
    
    public func setResult(result:Result) {
        if ready {
            return
        }
        
        self.result = result
        self.ready = true
        
        semaphore.signal()
    }
    
    public func getResult() -> String {
        objc_sync_enter(self)
        
        while !ready {
            objc_sync_exit(self)
            semaphore.wait()
            objc_sync_enter(self)
        }
        
        objc_sync_exit(self)
        
        return self.result!.getResult()
        
    }
}

つまり、getResultすると結果がsetResultされるまで待たされるという動きになる。

Resultは即に返答する。

public class Result : Request {
    private var content:String
    
    public init(_ command:Int) {
        print("request start \(command)")
        Thread.sleep(forTimeInterval: 1.0)
        content = "result = \(command)"
        print("request end \(command)")
    }
    
    public func getResult() -> String {
        return content
    }
    
}


リクエストを受けつたマネージャはまずFurureを返し、
遅延して受け取る結果をsetResultで入れる。

public class RequestManager {
    public func request(command:Int) -> Future {
        
        let future = Future()
        
        DispatchQueue.global().async {
            let result = Result(command)
            future.setResult(result:result)
        }
        
        return future
    }
}

ソースコードは以下に
github.com

回転させたときのレイアウト変更

XCode8で回転させた場合のレウアウト変更はSizeClassを使うとできる。
拘束条件を個別にinstallで設定する。全画面にしたい場合は上下左右にマージン0で設定してやる。

サイズの変更は以下の参考に書かれているような変更で呼び出しを受ける。
iOS8以降で、どうデバイスの回転を取り扱うかまとめてみる - Qiita