- 投稿日:2020-01-03T23:43:09+09:00
[Flutter] FlipCardを使ってカード型のWidgetを回転させてみる
やりたいこと
カード型のWidgetをタップするといい具合にアニメーションつけて裏側を表示するように回転したい。
使用したライブラリ
pub get
- pubspec.yamlに「flip_card: ^0.4.4」を追記して
flutter pub getする- dartソースに
import 'package:flip_card/flip_card.dart';を追記する実装
FlipCard( front: Card(...), // カード前面に表示するWidget back: Card(...), // カード背面に表示するWidget direction: FlipDirection.HORIZONTAL, // カード回転向き(HORIZONTAL:横<デフォルト>, VERTICAL:縦) flipOnTouch: true, // タッチ可否(true:カード部タップで回転する<デフォルト>, false:タップしても回転しない) speed: 500, // 回転スピード(500ミリ秒<デフォルト>) onFlip: () {}, // タップイベント onFlipDone: (isFront) {}, // タップイベント(前面かどうかbool値で判断できる) )サンプル
横回転
縦回転
実装サンプル
https://github.com/unbam/flip_card_sample
(おまけ)カード部分をタップではなくアイコンなどの別Widgetをタップすると回転するようにしたい
GlobalKeyを設定してtoggleCardメソッドを実行する。
final cardKey = GlobalKey<FlipCardState>(); FlipCard( key: cardKey, // キーを設定 front: Card( child: IconButton( icon: Icon(Icons.compare_arrows), onPressed: () => cardKey.currentState.toggleCard(), // 回転イベント実行 ) ) )
- 投稿日:2020-01-03T21:33:02+09:00
ブラウザが非表示になった時にマイク入力を解放する
困っている事
webAudioAPIを利用してマイク入力データを利用している中で
- 別タブに切り替えた時に、タブの録音中マークが消えない(PC)
- ホームボタンを押した時に画面上に赤い帯が残る(iOS)
環境
PC
- MacBook Pro 2016
- macOS Mojave 10.14.6
- Chrome 79.0.3945.88(Official Build) (64 ビット)
スマホ
- iPhone 6s
- iOS13.3
- safari
手順
- タブがアクティブじゃなくなった事を検知して
- マイクの入力トラックを解放する
タブがアクティブじゃなくなった事を検知
コード
function setEventListener() { let hidden, visibilityChange; if (typeof document.hidden !== "undefined") { // Opera 12.10 や Firefox 18 以降でサポート hidden = "hidden"; visibilityChange = "visibilitychange"; } else if (typeof document.msHidden !== "undefined") { hidden = "msHidden"; visibilityChange = "msvisibilitychange"; } else if (typeof document.webkitHidden !== "undefined") { hidden = "webkitHidden"; visibilityChange = "webkitvisibilitychange"; } document.hiddenStatus = hidden; if (hidden === undefined) { console.log("This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API."); } else { document.addEventListener(visibilityChange, browserBlorFunction, {once: true}); } } async function browserBlurFunction() { if (document.hidden) { // 非表示状態になった時の動作 await stopRecording(); } }解説
この記事に全て書いてあります!
[iOS/Android]ブラウザでページが非表示になったことを検知する方法今回はタブがアクティブではない時の体験を損なわない事を目的としていたので、
Page Visibility APIのみの利用をしています。簡単に言うと、「ページが表示されているかどうか」を判定してくれるAPIです。
EventListenerに登録して利用可能です!この機能によって追加されたプロパティである
document.hiddenで現在のブラウザの表示状態を取得できるので、
document.hidden === trueの時に、マイク入力解放関数を実行します。ちなみに
同じような動作をさせたい時のグローバルイベントハンドラーとして
window.onblurがあります。
これはウィンドウの切り替えを意味するものでありページの表示を検知するものではありません。
そのためwindow.onblurにマイク入力解放のコードをセットしても、
iPhoneでホームボタンを押した後は冒頭で紹介した赤い帯が出るし、
タブを切り替えてもブラウザの丸いマークは残ったままです。マイクの入力を解放する
コード
//streamsには、mediaStreamそのものが入っています。 function stopRecording() { let tracks = streams.getTracks(); tracks.forEach(function (track) { track.stop(); }); } //streamsへの格納 navigator.mediaDevices.getUserMedia({audio: true}) .then(mediaStream => { gotStream(mediaStream); }).catch(e => { alert('Error getting audio'); }); } let streams = null; function gotStream(stream){ streams = stream; //以下音声の処理... }解説
マイク入力はmediaDevices.getUserMediaより取得しており、その際に取得したmediaStreamTrackを停止させる事により、入力を解放します。
trackの停止
mediaStreamTrack.readyStateと言うプロパティの値が
"ended"になる時、入力デバイスからのデータを受け取らなくなります。"ended"は入力デバイスがこれ以上データを提供することがなく、新しいデータも一切提供されないことを示します。
引用元:mediaStreamTrack.readyStateそのための関数として、
MediaStreamTrack.stop()
トラックに関連付けられたソースの再生を停止し、ソースとトラックの関連付けを解除します。トラックの状態はendedに設定されます。こちらが用意されているので、それを使いましょう!
mediaStream.getTracksfunction stopRecording() { let tracks = streams.getTracks(); tracks.forEach(function (track) { track.stop(); }); }これでmediaStreamTrack.readyStateがendedになるため、記事冒頭で紹介した動作は起こらなくなります。
最後に
人生初Qiita記事書いてみました!
ちなみにsafariのアプリを完全に落とせば同じ事は達成できます()今年は頑張ってアウトプットしていくぞぉ
- 投稿日:2020-01-03T19:52:19+09:00
iOSのショートカットで扱う秘密データの管理方法について
はじめに
iOSのショートカットを作成する上で一度は悩むことは、ショートカット内部で扱う秘密データ(APIで扱うAPIキーやパスワードなどのトークン文字列)をどう管理するかではないでしょうか。
この管理方法について、検討した内容を共有したい思います。
前提
- iOSのショートカットを使用するため、iOS12以上が必要です。
- また、iPhoneの1passwordアプリを使用します。
よくある管理方法の問題
いろんな人が作成したショートカットの中身を見てみると、以下のように秘密データを管理しているケースに遭遇します。
- メモアプリ(あるいは リマインダーアプリ)に秘密データを保存し、ショートカット実行時にメモアプリから検索する。
- ショートカット内部に秘密データをハードコーディングする。
- ショートカット実行時にアラートを表示し、秘密データを入力させる。
メモアプリから検索する方法は、メモアプリがパスワードで保護されていないため、秘密データを他人に見られる恐れがあります。同様に、ハードコーディングする方法についても、ショートカットの内部処理を見れば、秘密データを他人に見られる恐れがあります。
ショーカット実行時に秘密データをアラートに表示する方法は、上記方法よりは良さそうに感じますが、秘密データをどこからか検索する手間やコピーしたクリップボードの秘密データが残ったままになる恐れがあります。
解決方法
上記の管理方法の解決方法として、1passwordのパスワード管理アプリを使う方法を提案したいと思います。
1passwordアプリで管理する利点としては、以下の通りです。
- 秘密データがマスターパスワードで保護されているため、他人に秘密データを容易に見られる心配がない。
- 1passwordのURLスキームを使えば、1passwordの秘密データを検索する(絞りこむ)ことができる。
- 例えば、
onepassword://search/<検索ワード>の形式で1passwordを起動できます。- URLスキームの仕様の詳細については、こちらを参照。
- 1passwordに秘密データが保存されていなくても、実行時に秘密データを作成できる。
- 1passwordの秘密データをワンタップでクリップボードでコピーできる。
ショートカットアプリでは1passwordアプリのURLスキームを実行時に呼び出すことができるため、以下のような手順で秘密データをショートカット内部で扱うことができます。
手順詳細
- ショートカットアプリから1passwrordアプリのURLスキームを起動する。
- 起動した1passwordアプリから秘密データをクリップボードにコピーする。
- クリップボードにコピー後、左上のアプリ名を押下し、ショートカットアプリに戻る。
- ショートカットアプリでクリップボードから秘密データを任意の変数にコピーする。
- 安全のためにクリップボードの秘密データをクリア(空文字列をコピー)する
- 任意の変数にコピーした秘密データをショートカット内部で使用する。
ショートカットの作成
ここでは、
1passwordアプリからGoogleのアカウントのトークンを表示するショートカットの作成例について説明します。手順詳細
- ショートカットの新規作成画面を開きます。
- アクションの
URLを開くを追加し、onepassword://search/googleをURLに設定します。
- この処理で、1passwordが起動し、
- スクリプティングの
待機を追加します。
- この処理で、1passwordのアプリからショートカットアプリに戻るまで、ショートカットアプリが待機状態になります。
- アクションの
変数に設定を追加します。
- 変数を仮にtokenとして、変数tokenをクリップボードに設定にします。
- アクションの
テキストを追加します。
- 変数tokenを使用して、秘密データを表示するテキストを作成します。
- ここでは、
トークンはXXXXですのような形式のテキストになります。- アクションの
クイックルックを追加します。
- 作成したテキストをクイックルックで表示します。
- クリップボード上の秘密データをクリアするために、アクションの
テキストを追加し、空のテキストを追加します。- 最後にアクションの
クリップボードにコピーを追加し、空のテキストをクリップボードにコピーします。参考までに、上記で作成したショートカットのiCloudの共有リンクを貼っておきます。
さいごに
秘密データの管理方法について、1passwordを活用する方法について提案しました。秘密データを管理する際の参考になれば幸いです。
参考
- 投稿日:2020-01-03T19:36:00+09:00
XCodeを使わず、MacApp「Tranceporter」でipaをAppStoreConnectに簡単にアップロード
納品されたiOSアプリケーションのipaファイルをXCode使わずにAppStoreConnectにアップロードすることができるTranceferというMac Appが公開されてたので使ってみます。
前提
- Apple Developerアカウントを保有している
- App Store ConnectのマイAppに新規アプリ作成していること
Mac App「Trancefer」
Mac App StoreでTransporterを入手できるようになりました
https://developer.apple.com/jp/news/?id=10152019aAppのリンクはこちら。期待できそう!
サインイン画面が表示されます。
AppStoreConnectのアカウントでサインインします。
ipaファイルをドラッグ&ドロップすると、以下のようにアプリがデリバリできる状態になります。
このままデリバリしてもいいのですが、デリバリ失敗すると証明書の中のバージョンを上げて再度デリバリし直したりしないといけなくなるので、デリバリ前に検証を行います。デリバリボタン横のオプションボタンをクリックし、[検証]を実行します。
では、AppStoreConnectにデリバリ転送します。[デリバリ]ボタンをクリックします。
正常に終わると以下のようにデリバリ済みという表示に変わります。
AppStoreConnectのApp画面を見ると、ちゃんと転送されてます。
参考リンク
- 投稿日:2020-01-03T14:15:44+09:00
コードによる制約の追加
趣味でiOSアプリ開発をかじっていた自分が、改めてiOS開発を勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。いつか書き直します。
参考文献
この記事は以下の書籍の情報を参考にして執筆しました。5章を読んでの内容になります。
コードによる制約の追加
3つの方法がある。
・NSLayoutConstraintのイニシャライザ
・VisualFormatLanguage
・NSLayoutAnchor(iOS9~)NSLayoutConstraintのイニシャライザ
メリット : 最も基本的な方法なのでわかりやすい。
デメリット : 制約1つを定義するのに1つイニシャライザを呼び出さないといけない。ViewContorollerlet view1 = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 300, height: 250)) view1.backgroundColor = .red view1.translatesAutoresizingMaskIntoConstraints = false view.addSubview(view1) /*constraint1 : viewとview1のcenterXを等しくする constraint2 : viewとview1のcenterYを等しくする constraint3 : view1の幅を設定 constraint4 : view1の高さを設定*/ // 制約を設定。幅と高さも必要。 let constraint1 = NSLayoutConstraint(item: view1, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0.0) let constraint2 = NSLayoutConstraint(item: view1, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0.0) let constraint3 = NSLayoutConstraint(item: view1, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, constant: 100) let constraint4 = NSLayoutConstraint(item: view1, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, constant: 100) view.addConstraints([constraint1, constraint2, constraint3, constraint4])
引数名 渡す値 item 制約を追加する部品 attribute 制約を追加する要素 relatedBy 追加する部品と基準となる部品の関係性 toItem 制約の基準となる部品 attribute 制約の基準となる要素(toItemの要素) multiplier 制約の割合の数値 constant 制約で追加する数値 relatedByがequalとして式で表すと
item.attribute = toItem.attribute * multiplier + constantVisualFormatLanguage
VSLと略されたり、視覚的書式言語と呼ばれることもある。
NSLayoutConstraintクラスのconstraintsメソッドを使う。戻り値は配列で帰ってくる。
メリット : 1つの処理で複数の制約を作成することができる。
デメリット : 制約を決める部分が文字列なので実行時エラーが出ない。ViewContorollerlet view1 = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 300, height: 250)) view1.backgroundColor = .red view1.translatesAutoresizingMaskIntoConstraints = false view.addSubview(view1) /*constraint1 : 水平方向の制約。親viewから100ptスペースを空けて幅を指定(metricsで値を指定したパターン) constraint2 : 垂直方向の制約。親viewから200ptスペースを空けて高さを指定*/ let constraints1 = NSLayoutConstraint.constraints(withVisualFormat: "H:|-100-[view1(width)]", options: .alignAllCenterX, metrics: ["width" : 200], views: ["view1" : view1]) let constraints2 = NSLayoutConstraint.constraints(withVisualFormat: "V:|-200-[view1(200)]", options: .alignAllCenterY, metrics: nil, views: ["view1" : view1]) // 2つの配列を1つの配列にするためにflatmapを使っている。 view.addConstraints([constraints1, constraints2].flatMap{ $0 })withVisualFormat
VHLフォーマットで制約を記述する
レイアウトの方向
記法 意味 V: 垂直方向の制約を追加 H: 水平方向の制約を追加 対象オブジェクト
記法 意味 [view] viewオブジェクト | 親ビュー オブジェクト間のスペース
記法 意味 |-20-[view] 親とviewの距離20pt |-(-20)-[view] 親とviewの距離-20pt |-[view] 親とviewの距離デフォルト(8pt) |[view] 親とviewの距離0pt オブジェクトのサイズ
記法 意味 H:[view(100)] viewの幅は100pt H:[view1(==view2)]view1の幅はview2に等しい
関係性 - Relationship
記法 意味 [view1]-(>=50)-[view2] view1とview2の距離は50pt以上 H:[view(>= 50, <=100)] viewの幅は50pt以上、100pt以下 優先度
記法 意味 H:[view(100@750)] viewの幅は優先度750で100pt options
複数のオブジェクトに対して揃え方を指定する。
例えば、V:[view1]-[view2]のように垂直方向の制約を定義する際は下記のオプションを選択する。
値 意味 .alignAllLeft 全てのビューを左寄せ .alignAllRight 全てのビューを右寄せ .alignAllLeading 全てのビューをLeading寄せ .alignAllTrailing 全てのビューをTrailing寄せ .alignAllCenterX 全てのビューをx方向中央に揃える 水平方向の場合
値 意味 .alignAllTop 全てのビューを上部寄せ .alignAllBottom 全てのビューを下部寄せ .alignAllCenterY 全てのビューをy方向中央に揃える .alignAllBaseline 全てのビューをベースラインに揃える またVFlで記述したレイアウトを左右どちらから並べるか指定するオプションもある。
基本はLeadingからTrailing方向に並べられる。
値 意味 .directionLeadingtoTrailing LeadingからTrailing方向に並べる .directionLeftToRight 左から右に並べる。 .directionRightToLeft 右から左に並べる。 metrics
変数を代入するのに使う。
例let constraints1 = NSLayoutConstraint.constraints(withVisualFormat: "H:[view1(width)]", options: .directionRightToLeft, metrics: ["width" : 200], views: ["view1" : view1])NSLayoutAnchor
メリット : 処理が読みやすい。比較的コードを書く量が少ない。
デメリット : iOS9以降からなのでiOS9以前では使えない。ViewContorollerlet view1 = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 300, height: 250)) view1.backgroundColor = .red view1.translatesAutoresizingMaskIntoConstraints = false view.addSubview(view1) view1.topAnchor.constraint(equalTo: view.topAnchor, constant: 200).isActive = true view1.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100).isActive = true view1.widthAnchor.constraint(equalToConstant: 200).isActive = true view1.heightAnchor.constraint(equalToConstant: 200).isActive = true制約を編集する
IBOutletで制約を紐づけることができ、コードでプロパティを変更できる。
@IBOutlet weak var blueViewTopConstraint: NSLayoutConstraint! @IBAction func tapButton(_ sender: Any) { blueViewTopConstraint.constant += 50 }空間を定義する - UILayoutGuide(iOS9.0~)
空間を定義して、その空間をもとに制約をつけることができる。
空のUIViewを用いるよりも、レンダリングコストが発生しない。余計なビューの階層が増えないというメリットがある。class ViewController3: UIViewController { override func viewDidLoad() { super.viewDidLoad() let view1 = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 300, height: 250)) view1.backgroundColor = .red view1.translatesAutoresizingMaskIntoConstraints = false view.addSubview(view1) // 空間を定義する let space = makeLayoutSpace() // spaceを基準に制約をつけることができる view1.topAnchor.constraint(equalTo: space.bottomAnchor, constant: 10).isActive = true view1.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100).isActive = true view1.widthAnchor.constraint(equalToConstant: 200).isActive = true view1.heightAnchor.constraint(equalToConstant: 200).isActive = true } func makeLayoutSpace() -> UILayoutGuide{ let activeFlag = true view.addLayoutGuide(space) space.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = activeFlag space.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = activeFlag space.widthAnchor.constraint(equalToConstant: 200).isActive = activeFlag space.heightAnchor.constraint(equalToConstant: 200).isActive = activeFlag return space }デバッグ画面で確認すると定義した空間からの制約は見えず、viewの階層にも空間は存在しないが、確かに定義した空間からの制約をもとにviewを表示できている。
- 投稿日:2020-01-03T14:14:29+09:00
Storyboardの設定項目
趣味でiOSアプリ開発をかじっていた自分が、改めてiOS開発を勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。いつか書き直します。
参考文献
この記事は以下の書籍の情報を参考にして執筆しました。4章を読んでの内容になります。
起動時のstoryboardを設定
GUIで設定
プロジェクトファイルのMain Interfaceでstoryboardを指定する。
下記のように設定してあるとMain.storyboardファイルが読み込まれる。storyboardで開始時のビューを設定する方法
矢印をドラッグして持ってくるか、is initial View Controller にチェックを入れる。
stackボタン
部品を複数選択して下記のボタンを押すとstackViewを作ることができる。
親子関係にあるビューの制約
子ビューから親ビューへCtrol+ドラッグした時に追加できる制約で、親子間での比率など制約を設定できる。
xibファイ ルの呼び出し
呼び出したい箇所で読み込む。
(guardでnilのチェックをしているが他の方法でもいい)
// xibファイルの呼び出し guard let subView = Bundle.main.loadNibNamed("Xibファイル名", owner: nil, options: nil)?.first as? UIView else { return } self.view.addSubview(subView)カスタムクラスのビューに指定し、カスタムクラスを呼び出す。
xibファイルに属性を追加したりできる。
XibView.swiftimport UIKit class XibView: UIView { override init(frame: CGRect){ super.init(frame: frame) loadNib() } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! loadNib() } func loadNib(){ // xibファイル読み込み guard let view = Bundle.main.loadNibNamed("View2", owner: nil, options: nil)?.first as? UIView else { return } view.frame = self.bounds self.addSubview(view) } }使い方例
let subView = XibView(frame: CGRect(x: 50, y: 50, width: 400, height: 700)) self.view.addSubview(subView)storyboard分割
Storyboard Referenceを用いてstoryboardを分割し、参照することができる。
分割したい範囲をまとめて選択し、「Editor」→「Reference to Stroryboard」を選択し、新しいstoryboardを名前をつけて保存。
選択した範囲だけで構成される storyboardができた。
分割元の方では参照するstoryboardが表示されている。
- 投稿日:2020-01-03T14:10:46+09:00
UIViewControllerのレイアウトに関する処理
趣味でiOSアプリ開発をかじっていた自分が、改めてiOS開発を勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。いつか書き直します。
参考文献
この記事は以下の書籍の情報を参考にして執筆しました。3章を読んでの内容になります。
UIViewControllerとレイアウトをサポートするクラス
UIViewにおけるレイアウトのサイクル
制約の更新
フレームの更新
レンダリングの3ステップ。
制約の更新
制約が変更されるとレイアウトにおけるコンポーネントの位置関係が更新されるので、レイアウトエンジンがその条件を満たすための再計算を行う。
UIViewのupdateConstrains()が呼ばれ子から親ビュー(ボトムアップ)に制約の計算が行われる。制約の更新の条件
・制約のacticeフラグによる有効・無効化。
・制約の優先度変更
・制約の変更・追加・削除。
・制約を与えられたビューの階層変更。明示的に制約を更新する方法
・self.updateConstraintsIfNeed()を呼ぶ。呼んだ瞬間更新される。
・setNeedUpdateConstraints()を呼ぶ。制約を更新する必要があるフラグが立ち、次のレイアウトパスで再計算される。フレームの更新
制約情報が更新されると、レイアウトを更新するためにレイアウトエンジンが計算したフレーム情報をビューが受け取り、layoutSubviews()が呼ばれ、親から子(トップダウンに)にフレームの更新が実施される。またlayoutSubviews()が呼ばれるとupdateConstraintsIfNeedが呼ばれる。
フレーム更新を引き起こす条件の例
・ビューのフレームの変更。
・サービューの追加・削除。
・UIScrollviewのサブクラスにおいてcontentOfSetが変更された時。明示的にフレームを更新する方法
・self.layoutIfneeded()。
呼んだ瞬間更新される。
・setNeedsLayout()を呼ぶ。フレームを更新する必要があるフラグが立ち、次のレイアウトパスで再計算される。レンダリング
更新されたフレーム情報をレンダリングする。ディスプレイに変更を表示するためにdrawRectメソッドが呼ばれる。
レンダリングのタイミング
・ビューの一部を隠している別のビューの移動または削除。
・hiddenになっていたビューの再表示。
・ビューの画面外までスクロールし、再び画面内に戻す。
・ビューのsetNeedDisplayメソッドまたはsetNeedsDisplayInRect(_ :)メソッドの明示的な呼び出し。ViewControllerのライフサイクル
ViewController生成
loadView()
viewDidLoad()
viewWillAppear()
viewWillLayoutSubviews()
viewDidLayoutSubviews()
viewDidAppear()
表示完了
viewillDisappear()
viewDidDisappear()
ViewController解放loadView()
ビューをメモリに読み込み、対象のポインタと関連付けを行うメソッド。
storyboardが存在する場合、storyboardで定義されたビューがセットされる。
storyboardを用いずにカスタムビューを定義する場合はloadviewをオーバーライドし、設定を行う必要がある。override func loadView() { // 親ビューの定義 let contentView = UIView(frame: UIView().bounds) contentView.backgroundColor = UIColor.black self.view = contentView // addSubViewでボタン追加とか制約追加したり }viewDidLoad
loadViewが完了した時に呼ばれる。
loadViewで読み込みが完了したオブジェクトにプロパティをセットしたり、クラス内で用いるオブジェクトの初期化をする。viewWillAppear()
ビューが表示される直前に呼ばれる。
バックグラウンドからアプリを開き直したり、UITabBarControllerにおけるタブの切り替え時も呼ばれる。
この段階ではユーザにUIは提供されていないので計算コストが高い処理は避ける。viewWillLayoutSubView()
ビューのレイアウト開始時に呼ばれる。
ビューコントローラが読み込まれた時だけでなく、端末の回転やビューの再表示によってビューが新しい大きさになった時に必ず呼ばれる。viewWillLayoutSubView()が呼ばれた後、ビューコントローラーのレイアウトが開始される。
このタイミングで、UIViewのレイアウトサイクルでいう制約の更新、フレームの更新が行われる。viewDidLayoutSubviews()
ビューのレイアウトが完了した時に呼ばれる。
この時点で、UIViewのレイアウトサイクルでいうフレームの更新が行われているので、ビューコントローラーの持つビュー内のレイアウトが確定する。viewDidAppear()
ビューが表示された直後に呼ばれる。
この時点で、UIViewのレイアウトサイクルでいうレンダリングが行われている。
UIがユーザに提供されているのでUXに直接影響する処理はしない。
viewWillAppear()と対になっているため、バックグラウンドから復帰したり、UITabBarControllerにおけるタブの切り替え時も呼ばれる。viewillDisappear
ビューが階層から削除されたときに呼び出される。
このメソッドをオーバーライドしてビューの非表示、非表示に関する追加の処理を実行できる。viewDidDisappear
ビューが階層から削除される前に呼び出される「。
このメソッドをオーバーライドして編集の変更をコミットしたり。削除に関連する処理を実行できる。UIWindow
UIWindow : ディスプレイにおけるウィンドウの役割を担っている。
例えばキーボードやUIAlertViewは新しいウィンドウを生成して必要な部品を生成している。keyboard表示時にデバックした時の画面
Windowの重なり順
UIWindowのwindowLevelプロパティの値が大きいものが上に表示される。
UIKitではNormal,Alert,StatusBarの3種類が定義されており、アラートの値が一番大きい。UIWindowLevelNormal == 0.0 UIWindowLevelAlert == 2000.0 UIWindowLevelStatusBar == 1000.0Windowの画面サイズ
UIScreenサイズを取得する方法
この方法で画面のサイズは取得できる。let screenSize = UIScreen.main.boundsしかしiOS9.0からiPadにマルチタスク機能が搭載されたので、スクリーンサイズとウィンドウサイズが違う場合がでてきた。
よってWindowのサイズを取得する事で正確な値をとる。let winodwSize = UIApplication.shared.keyWindow?.boundsiPadでマルチタスクにしたとき、オレンジの枠がscreenSizeで赤の枠がwindowSizeとなる。
UIStackViewとAutoLayout
UIStackViewの中にオブジェクトを並べると自動で並べてくれるが、このオブジェクトには自動的に制約が付与されている。
stackViewにのみ制約をつけて、中にLabel、UIimage、UITextViewを入れる。
表示する画像を決めると中のオブジェクトの制約は自動的に決まる。オブジェクトの配置方法はUIStackViewのプロパティによって決まる。
Spacing : オブジェクト間の距離
Axis : 並べる方向。水平か垂直。
Alignment : 揃え方。軸方向での中央揃え、左揃え、右揃え、大きさを最大にして並べる(Fill)。
(下記の例では垂直方向の例しか取り扱っていないが水平方向の場合そのまま横にしてみたときと同じ)Distribution : 並べ方
Fill : Intrinsic Content Sizeを満たしながら、軸方向にサブビューを並べる。(コンテンツの大きさに合わせて幅が決まる)
Fill Equally : 軸方向に均等に高さを指定して並べる。
Fill Proportionaally : Intrinsic Content Sizeの比率に合わせた高さで、軸方向にサブビューを並べる。(コンテンツの比率に合わせて幅が決まる)
Equal Spacing : Intrinsic Content Sizeを変更せずに、軸方向におけるサブビューのスペースを均一にして並べる。
Equal Centering : Intrinsic Content Sizeの大きさのサブビューたちの中心距離が均一になるように並べる。
FillとFill Proportionaallyの違い
stackViewの高さをコンテンツが入らない範囲で固定にすると、Fillの方は高さの制約が必要になるのに対して、Fill Proportionaallyは比率に合わせてコンテンツを小さくした。
- 投稿日:2020-01-03T14:06:47+09:00
Auto Layoutの基本
趣味でiOSアプリ開発をかじっていた自分が、改めてiOS開発を勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。いつか書き直します。
参考文献
この記事は以下の書籍の情報を参考にして執筆しました。1,2章を読んでの内容になります。
Adaptivityなレイアウトを実現する
画面サイズの多様化により、どのようなウィンドウサイズでも対応できる柔軟性のあるレイアウトが求められるようになった。ウェブ世界のレスポンシブレイアウトとほぼ同じ意味合いで用いられます。
Auto Layoutやトレイコレクションという概念。
Auto Layout : コンテンツをベースとしたレイアウト方法。
トレイトコレクション : 画面情報を抽象的なオブジェクトとして扱う概念。サイズクラスなど。サイズクラス
画面の高さと幅を抽象的に示す。
端末と向き 高さ 幅 iPad縦 Regular Regular iPad横 Regular Regular iPhone縦 Regular Compact iPhone横 Compact Compact iPhonePlus縦 Regular Compact iPhonePlus横 Compact Regular Auto Layoutの基本概念
制約式
Auto Layoutは制約を表した数式である制約式の連立方程式を解くことでレイアウトを決定している。
例1
画像のButton2はButton1から50pt下の座標にある。
この時の制約式
Button2.top = Button1.bottom + 50storyboardのContaintsで制約の式が観れる。
例2
緑のViewの上端と右端、左端を親ビューに固定し、緑ビューと親ビューの下端に20%のマージンを置く。
この時の制約式
greenView.top = superView.top greenView.left = superView.left greenView.right = superView.right greenView.bottom = 0.8 * superView.bottomこれらの線形式を一般化すると次の式になる。
y = ax + bAuto Latoutでは上記の式を複数持つ線型方程式を解いている。
Auto Layoutの基本概念
オブジェクトの要素
Leading, Trailing
日本語や英語ではRight, Leftと同値だが、アラビア語などではLeftとRightが反転してしまうので。基本的にはこちらが使われる。
Center X, Center Y
x座標,、y座標。
Baseline
テキストを整列するための基準線。文字列最初のBaselineのみFirstBaselineで示される。
gなどBaselineをはみ出す文字ように空白を調整したりする時など使える。NotAnAttribute
比較オブジェクトがない場合も要素を渡す必要があるので、代わりにNotAnAttributeを使う。
Relation - 要素の関連性
Relationは制約式 y = ax +b の「=」の部分を示している。
enum NSLayoutReration : Int { case LessThanOrEqual // 制約より小さな値でレイアウトを実施 case Equal // 制約通りの... case GreaterThanOrEqual // 制約より大きな... }Priority - 優先度
1~1000の優先度を設定することができる。標準の値は1000で最大。
Intrinsic Content Size - 固有の寸法
UIButtonなどの文字列の長さ、フォントのサイズによって大きさを変えるオブジェクトに使われている。
値を取得することも可能。Content Hugging Priority, Content Compression Resistance Priority - コンテンツに対する優先度
「大きくなりにくさ」と「小さくなりにくさ」を定義している
vertical : 縦方向
horizontal : 横方向storyboardから設定ができ、それぞれ優先度を指定できる。
優先度より低い制約では大きさが変わらない。
- 投稿日:2020-01-03T10:58:51+09:00
[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターン ~Composite~
この記事シリーズは、iOS/Swiftエンジニアである執筆者個人が、
ごく普通のiOSアプリ開発でよくある状況や
Swiftのコアライブラリやフレームワークで使われているパターンに
着目してデザインパターンを学び直してみた記録です。関連記事一覧
[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターンCompositeパターン概要
- ディレクトリとファイルのような、ツリー構造を扱うためのパターンです。
- ディレクトリとファイルのように、「名前」などの同じプロパティや、「削除」などの同じ操作を持つ場合、ディレクトリ(容器)とファイル(中身)を同じように扱うことができます。
- GoFのデザインパターンでは構造に関するパターンに分類されます。
使い所
- そのものズバリ、ツリー構造を扱う場合には、Compositeパターンを思い浮かべると設計に掛かる時間を短縮できる可能性が高いです。
- UIViewのView Hierarchyもツリー構造であり、Compositeパターンが使われています。
引用:Cocoa Design Patterns (Retired Document)サンプルコード
Swiftバージョンは 5.1 です。
protocol DirectoryEntry { var name: String { get } func remove() } final class File: DirectoryEntry { let name: String init(name: String) { self.name = name } func remove() { print("\(name)を削除しました") } } final class Directory: DirectoryEntry { let name: String private var entryList = [DirectoryEntry]() init(name: String) { self.name = name } func add(entry: DirectoryEntry) { entryList.append(entry) } func remove() { for entry in entryList { entry.remove() } print("\(name)を削除しました") } } // Usage let dir1 = Directory(name: "dir1") let file1 = File(name: "file1") dir1.add(entry: file1) // dir1 // ∟file1 let dir2 = Directory(name: "dir2") let file2 = File(name: "file2") let file3 = File(name: "file3") dir2.add(entry: file2) dir2.add(entry: file3) // dir2 // ∟file2 // ∟file3 dir1.add(entry: dir2) // dir1 // ∟file1 // ∟dir2 // ∟file2 // ∟file3 let file4 = File(name: "file4") dir1.add(entry: file4) // dir1 // ∟file1 // ∟dir2 // ∟file2 // ∟file3 // ∟file4 dir1.remove() // "file1を削除しました" // "file2を削除しました" // "file3を削除しました" // "dir2を削除しました" // "file4を削除しました" // "dir1を削除しました"
- 投稿日:2020-01-03T10:13:42+09:00
[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターン ~Singleton~
この記事シリーズは、iOS/Swiftエンジニアである執筆者個人が、
ごく普通のiOSアプリ開発でよくある状況や
Swiftのコアライブラリやフレームワークで使われているパターンに
着目してデザインパターンを学び直してみた記録です。関連記事一覧
[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターンSingletonパターン概要
- インスタンスが1個しか生成されないことを保証するためのパターンです。
- GoFのデザインパターンでは生成に関するパターンに分類されます。
使い所
- iOSフレームワーク内で随所に使われています。典型例は
UIApplication.sharedです。- 実行時に依存性を変更しづらく、ユニットテストがしにくいというデメリットがあります。
- またグローバル変数と同様に、いつ・どこから・どのように変更されるのか理解が難しくなりますので、注意しながら利用する必要があります。
サンプルコード
Swiftバージョンは 5.1 です。
class SingletonClass { class var shared : SingletonClass { struct Static { static let instance : SingletonClass = SingletonClass() } return Static.instance } } // Usage let instance = SingletonClass.shared※少々回りくどい実装になっていますが、マルチスレッドでインスタンスが複数生成されてしまうことを回避するための実装例です。
- 投稿日:2020-01-03T08:26:54+09:00
bazelのiosチュートリアルの修正点
チュートリアルの左上にあるボタンからmasterに切り替えればよかった
ーーーーー
基本的にはこの手順で良い
https://docs.bazel.build/versions/2.0.0/tutorial/ios-app.htmlただ、依存ライブラリが古いまま(2020/1/3現在)なので若干記法などが異なる。
チュートリアルの中で出てくるWORKSPACEとBUILDを次のように変更すればとりあえずビルドまでは行ける。WORKSPACEworkspace(name = "tutorial") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "bazel_skylib", urls = [ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz", "https://github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz", ], sha256 = "97e70364e9249702246c0e9444bccdc4b847bed1eb03c5a3ece4f83dfe6abc44", ) load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") bazel_skylib_workspace() # iOS basic build deps. http_archive( name = "build_bazel_rules_apple", sha256 = "bdc8e66e70b8a75da23b79f1f8c6207356df07d041d96d2189add7ee0780cf4e", strip_prefix = "rules_apple-b869b0d3868d78a1d4ffd866ccb304fb68aa12c3", url = "https://github.com/bazelbuild/rules_apple/archive/b869b0d3868d78a1d4ffd866ccb304fb68aa12c3.tar.gz", ) load( "@build_bazel_rules_apple//apple:repositories.bzl", "apple_rules_dependencies", ) apple_rules_dependencies() load( "@build_bazel_rules_swift//swift:repositories.bzl", "swift_rules_dependencies", ) swift_rules_dependencies() load( "@build_bazel_apple_support//lib:repositories.bzl", "apple_support_dependencies", ) apple_support_dependencies()BUILDload("@build_bazel_rules_apple//apple:ios.bzl", "ios_application") objc_library( name = "UrlGetClasses", srcs = [ "UrlGet/AppDelegate.m", "UrlGet/UrlGetViewController.m", "UrlGet/main.m", ], hdrs = glob(["UrlGet/*.h"]), data = ["UrlGet/UrlGetViewController.xib"], ) ios_application( name = "ios-app", bundle_id = "Google.UrlGet", families = [ "iphone", "ipad", ], minimum_os_version = "9.0", infoplists = [":UrlGet/UrlGet-Info.plist"], visibility = ["//visibility:public"], deps = [":UrlGetClasses"], )



















































