- 投稿日:2019-05-27T23:13:40+09:00
Swiftのバージョンアップ手順(Swift 4.2→5.0)
はじめに
個人アプリでSwiftを4.2から5.0にバージョンアップし、そのときの手順をまとめました。
Xcodeの更新
SwiftはXcodeのバージョンと紐付いているため、App StoreからXcodeをアップデートすることで、Swiftをバージョンアップします。
ライブラリの更新
ライブラリ管理ツールと、そのツールで管理しているライブラリを更新します。
エラーや警告が発生しない限り、更新しなくても問題ないことが多いですが、私はSwiftのバージョンアップと同時に更新しています。
ただ、Swiftの新バージョンのリリース直後はライブラリが対応していないこともあります。私はCocoaPodsとCarthageを使っているので、その更新のみ紹介します。
この記事では省略していますが、必要に応じて「Podsfile」や「Cartfile」を書き換えたり、全ライブラリでなく必要なライブラリのみ更新したりしてください。# CocoaPodsの更新 # 「/usr/local/bin」にインストールしている場合 $ sudo gem update -n /usr/local/bin cocoapods # CocoaPodsで管理している全ライブラリの更新 $ pod update # Carthageの更新 # Homebrewでインストールしている場合 $ brew upgrade carthage # Carthageで管理している全ライブラリの更新 $ carthage update --platform iOS警告の解消
Xcodeで発生している警告を解消します。
基本的には警告をダブルクリック→自動修正でOKです。プロジェクトファイルの更新
以下の警告は、プロジェクトファイルを更新することで解消します。
Swift Conversion Conversion to Swift 5 is available全ターゲットのチェックがONになっていることを確認し、[Next]ボタンをクリックします。
これで修正が完了です。
自動修正の適用後、プロジェクトファイルは以下のように更新されます。(一部のみ抜粋)
project.pbxproj… - objectVersion = 50; + objectVersion = 51; … + LastSwiftMigration = 1020; … - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; …スキーマの更新
以下の警告は、スキーマを更新することで解消します。
Validate Project Settings Update to recommended settings自動修正の適用後、スキーマは以下のように更新されます。(一部のみ抜粋)
{プロジェクト名}.xcscheme… - LastUpgradeVersion = "1010" + LastUpgradeVersion = "1020" …Migrate “English.lproj”
Migrate “English.lproj” (Deprecated) Pods.xcodeprojこれで修正が完了です。
自動修正の適用後、Git上では更新が確認できませんでした。
私がバージョン管理していないファイルが更新されたようです。Enable Base Internationalization
Enable Base Internationalization Pods.xcodeprojこれで修正が完了です。
自動修正の適用後、こちらもGit上では更新が確認できませんでした。
ACL(アクセス制御レベル)の省略
親と同等のACLを明記しているメソッドは警告が表示されるようになったようです。
'public' modifier is redundant for instance method declared in a public extension Replace 'public ' with ''public extension UIViewController { - public func showAlert(title: String, message: String, actions: [UIAlertAction]) { + func showAlert(title: String, message: String, actions: [UIAlertAction]) { }メソッドから
public
が削除されました。おわりに
小規模の個人アプリなので、エラーや警告が少なく、自動修正のみで対応が完了しました。
- 投稿日:2019-05-27T20:57:59+09:00
PythonistaでGPS
はじめに
PythonistaでGPSが使えるらしいので試してみた。
実装
import location location.start_updates() # GPSデータ更新を開始 gps_data=location.get_location() # GPSデータを取得する location.stop_updates()# GPSデータ更新を終了 print(gps_data['latitude']) print(gps_data['longitude'])結果
かなり簡単にGPS情報を取得できた。
しかし、このままでは緯度、経度の生データなのでどこなのかはわからない。
住所変換ライブラリもあるらしいが、Pythonistaで使えるのだろうか?
- 投稿日:2019-05-27T18:49:05+09:00
Swift5での正方形カメラ
はじめに
Swift5で正方形の写真を撮影するカメラの作り方を勉強してみました。
iPhoneのWidthと同じ長さの正方形の写真をカメラロールに保存してみます。カメラアプリを作る2種類の方法
・UIImagePickerController
簡単にカメラで撮影するアプリが作れます。正方形で撮影するのは難しそう。
・AVFoundation
動画を撮影したり、いろいろできる賢いやつです。←今回はこちらです。
実装方法
あらかじめinfo.plistに二つの記述が必要です。
・Privacy - Camera Usage Description
「カメラを使います」的な内容を記載
・Privacy - Photo Library Additions Usage Description
「カメラロールに保存します」的な内容を記載ソースコード
ViewController.swiftimport UIKit import AVFoundation class ViewController: UIViewController, AVCapturePhotoCaptureDelegate { var device: AVCaptureDevice! var session: AVCaptureSession! var output: AVCapturePhotoOutput! var tempImage:UIImage! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. setPicture() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } //カメラ準備 func setPicture(){ //セッションを生成 session = AVCaptureSession() //背面カメラを選択 device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) //背面カメラからキャプチャ入力生成 guard let input = try? AVCaptureDeviceInput(device: device) else { print("例外発生") return } session.addInput(input) output = AVCapturePhotoOutput() session.addOutput(output) session.sessionPreset = .photo // プレビューレイヤーを生成 let pvSize = self.view.frame.width let pvLayer = AVCaptureVideoPreviewLayer(session: session) pvLayer.frame = view.bounds pvLayer.frame = CGRect(x: 0, y: 90, width: pvSize, height: pvSize) pvLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill view.layer.addSublayer(pvLayer) // セッションを開始 session.startRunning() // 撮影ボタンを生成 let shutterBtn = UIButton() shutterBtn.setTitle("◯", for: .normal) shutterBtn.titleLabel?.font = UIFont.systemFont(ofSize: 74) shutterBtn.contentMode = .center shutterBtn.frame = CGRect(x: 0, y: 0, width: 80, height: 80) shutterBtn.layer.cornerRadius = 0.5 * shutterBtn.bounds.size.width shutterBtn.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) shutterBtn.setTitleColor(UIColor.black, for: UIControl.State()) shutterBtn.layer.position = CGPoint(x: view.frame.width / 2, y: self.view.bounds.size.height - 80) shutterBtn.addTarget(self, action: #selector(photoshot), for: .touchUpInside) view.addSubview(shutterBtn) //キャンセルボタンを生成 let cancelBtn = UIButton() cancelBtn.setTitle("×", for: .normal) cancelBtn.titleLabel?.font = UIFont.systemFont(ofSize: 32) cancelBtn.contentMode = .center cancelBtn.frame = CGRect(x: 0, y: 0, width: 40, height: 40) cancelBtn.setTitleColor(UIColor.white, for: UIControl.State()) cancelBtn.layer.position = CGPoint(x: 20, y: 40) cancelBtn.addTarget(self, action: #selector(cancelAction), for: .touchUpInside) view.addSubview(cancelBtn) } //撮影 @objc func photoshot(_ sender: AnyObject) { let settings = AVCapturePhotoSettings() settings.flashMode = .off settings.isAutoStillImageStabilizationEnabled = true output.capturePhoto(with: settings, delegate: self) } //撮影結果・再撮影・保存ボタンの表示 func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if error == nil { let outputImageView = UIImageView() let pvSize = self.view.frame.width outputImageView.frame = CGRect(x: 0, y: 90, width: pvSize, height: pvSize) outputImageView.image = UIImage(data: photo.fileDataRepresentation()!)?.croppingToCenterSquare() tempImage = UIImage(data: photo.fileDataRepresentation()!)?.croppingToCenterSquare() self.view.addSubview(outputImageView) session.stopRunning() //再撮影ボタン let retryBtn = UIButton() retryBtn.setTitle("再撮影", for:.normal) retryBtn.frame = CGRect(x:self.view.frame.width/2 - 150,y:self.view.frame.height - 115,width: 70,height: 70) retryBtn.addTarget(self, action: #selector(retryPhoto), for: .touchUpInside) view.addSubview(retryBtn) //保存ボタン let saveBtn = UIButton() saveBtn.setTitle("保存", for: .normal) saveBtn.frame = CGRect(x:self.view.frame.width/2 + 80,y:self.view.frame.height - 115,width: 70,height: 70) saveBtn.addTarget(self, action: #selector(savePhoto), for: .touchUpInside) view.addSubview(saveBtn) } } //再撮影 @objc func retryPhoto(sender:UIButton){ let subViews = view.subviews for subView in subViews { subView.removeFromSuperview() } setPicture() } //カメラロールへの保存 @objc func savePhoto(sender:UIButton){ UIImageWriteToSavedPhotosAlbum(tempImage, self, nil, nil) let subViews = view.subviews for subView in subViews { subView.removeFromSuperview() } setPicture() } //キャンセル @objc func cancelAction(sender:UIButton){ // } } extension UIImage { func croppingToCenterSquare() -> UIImage { let cgImage = self.cgImage! var newWidth = CGFloat(cgImage.width) var newHeight = CGFloat(cgImage.height) if newWidth > newHeight { newWidth = newHeight } else { newHeight = newWidth } let x = (CGFloat(cgImage.width) - newWidth)/2 let y = (CGFloat(cgImage.height) - newHeight)/2 let rect = CGRect(x: x, y: y, width: newWidth, height: newHeight) let croppedCGImage = cgImage.cropping(to: rect)! return UIImage(cgImage: croppedCGImage, scale: self.scale, orientation: self.imageOrientation) } }環境
・Xcode 10.2.1
・Swift 5
・iOS 12.3.1参考文献
おわりに
Mahalo
- 投稿日:2019-05-27T16:11:56+09:00
Swift 5でユニバーサルリンク(Universal link)でアプリを開いても、何も起きない時
こんにちは、こげばんです
Swift5にバージョンアップした時に起きた問題になりました。タイトルの通り、
ユニバーサルリンクを開いて、アプリを立ち上げたけど何も画面遷移しないし、動かないくて少し困りました。
- 原因
- 対処方法
- 影響範囲
1. 原因
元々のメソッドは、下記を使っていました。
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL, let _ = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return } print(url) }buildしながら見てみると、ユニバーサルリンクでアプリに来た時に稼働していないことに気がつきました。
リリース直前に気が付いて、焦っていましたw
Swift5にバージョンアップしたタイミングで、メソッドの呼ばれ方が変わったんだと思います。。。。2. 対処方法
そこでいろいろ見ていましが、teratailのこちらを参考にしました。
https://teratail.com/questions/171281
下記のように修正しました。func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL, let _ = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return false } print(url) } ⬇️ func application(_ application: UIApplication, continue continueUserActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { guard continueUserActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = continueUserActivity.webpageURL, let _ = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return false } print(url) }そしたら動くようになりました!
めでたしめでたし〜3. 影響範囲
今のところは、ユニバーサルリンクの範囲でそれ以上は発生していない感じです。
何か新しいことがあったら、追記していきたいと思いますmm
- 投稿日:2019-05-27T13:18:31+09:00
2FAが必須になった今、CircleCIでfastlaneを使ってdsymのダウンロード/アップロードはできなさそう
タイトルの通りCircleCIでは実現が難しいことがわかったので紹介します。
背景として、先日、AppleIDの2FAが必須になりました。fastlaneは2FAにも対応しており、詳しくは下記に記載があります。
Continuous Integration - fastlane docs #authentication-with-apple-services
具体的には
fastlane spaceauth -u user@email.com
を実行し、出力された値を環境変数に設定するというものです。
ただし、上記を実行した環境と利用する環境でregionが違うと失敗する可能性があるようで、私の環境ではCircleCIに上記で得られた値を設定しても正常に動作しませんでした。少し古いISSUEになりますが、2FAを無効にすると良いという回答もあります。
Locally generated FASTLANE_SESSION does not work on CI machine · Issue #9518 · fastlane/fastlane
今回はAccount Holderではないアカウントを使用したのですが、そのアカウントも2FAを無効にできず、その解決策が使えなくなっているようでした。
ちなみに、Bitraseでは2FAを行う環境が用意されているようです。
App Store Connect 2FA solved on Bitrise | Bitrise Blog
- 投稿日:2019-05-27T13:05:22+09:00
Flutterウィークリー #60
Flutterウィークリーとは?
FlutterファンによるFlutterファンのためのニュースレター
https://flutterweekly.net/この記事は#60の日本語訳です
https://mailchi.mp/flutterweekly/flutter-weekly-60※Google翻訳を使って自動翻訳を行っています。翻訳に問題がある箇所を発見しましたら編集リクエストを送っていただければ幸いです。
読み物&チュートリアル
PeanutであなたのFlutter / Dart Webアプリを披露する
https://medium.com/@kevmoo/show-off-your-flutter-dart-web-app-with-peanut-c0307f2b733c
Kevin Mooreが、自分のWebプロジェクトをghページに簡単に公開するために作成したCLIユーティリティ、Peanutの使い方を説明します。ペディスティックDart
https://medium.com/dartlang/pedantic-dart-1c7d365510de
David Morganが、Pedantic、Googleの内部で使用されている糸くずの構成、およびそれを構築するために行った決定について話します。CodemagicでFlutter Webアプリケーションを構築してホストする
https://blog.codemagic.io/build-and-host-your-flutter-web-apps-on-codemagic/
Codemagicの人々は、新機能、静的ページをリリースし、Codemagic CIからFlutter Webビルドをデプロイする方法を説明します。CircularRevealAnimationをFlutterライブラリとして実装し、その途中でpub.devに公開する方法
Alexander Zhdanovが、Circularの啓示を作成し、それをpub.devのライブラリとして公開する方法を説明しています。初心者のためのFlutterでFuturesを使うためのガイド
https://medium.com/flutter-community/a-guide-to-using-futures-in-flutter-for-beginners-ebeddfbfb967
あなたがそれらを初めて使うとき、Futuresは少し気が遠くなるかもしれません。 Dane Mackierによるこの記事で、あなたは短時間でプロのようにそれらを使用するでしょう。Widget-Async-Bloc-Service: Flutterアプリのための実用的なアーキテクチャ
Andrea BizzottoがBLoCアーキテクチャに新たな工夫を加え、彼が行った微調整を実用的なパターンにすることを説明します。マテリアルスタイルのセグメント化コントロール
https://medium.com/@sebbenkra/a-material-styled-segmented-control-1e92310c3a67
Sebastian KraatzはFlutterでセグメント化されたコントロールを必要としていたので、彼はそれを作り、自分でそれを行う方法を説明しました。Flutterクリッピング
https://medium.com/flutter-community/clipping-in-flutter-e9eaa6b1721a
Raouf Rahicheによるこの記事で、クリッピングの力を解き放ってください。Flutter PageViewのスクロールとウィジェットアニメーションの同期
AntonelloGalipòによる、アニメーションとPageViewスクロールとの接続方法に関するチュートリアル。Flutterテキストリーダーアニメーション
https://medium.com/aubergine-solutions/text-reader-animation-in-flutter-12f81a47ec7f
Pinkesh Darjiは、Google I / Oに表示されるアニメーションを再現して、AIがテキストを読み取る方法をシミュレートします。FlutterレスポンシブUIを構築する
https://medium.com/flutter-community/build-responsive-uis-in-flutter-fd450bd59158
モバイル、Web、デスクトップ用のFlutterアプリを作成するには、レスポンシブUIを作成する必要があります。 Raouf Rahicheがあなたに基本とFlutterからそれを処理する方法を示します。なぜFlutterようなツールが成功する必要があるのか
https://medium.com/flutter-community/why-tools-like-flutter-need-to-succeed-965a9408e8dc
Ryan Edgeによる開発作業と、なぜFlutter (およびその他のフレームワーク)が存在すべきかについての興味深い考察。実行時にテーマを変更する
https://medium.com/@bimsina/changing-theme-at-run-time-flutter-d634c307de8a
ダークモードはMaterialスペックの中の1つですので、Bibek Timsinaが実行時にアプリのテーマを変更する方法を説明します。Flutterウィジェットパーフェクトステート管理出来ますか?
州の管理を容易かつ効果的にするためのMellati Meftahによる努力。Flutter - バウンスボタンアニメーション
https://medium.com/flutter-community/flutter-bouncing-button-animation-ece660e19c91
アニメーションボタンの作成に関するDaniele Cambiによるチュートリアル。Shimmerパッケージを使ったFlutter簡単なスケルトンビュー
情報をロードしている間にスケルトンを表示することは最近かなり流行しています。 Dane MackierがFlutterそれを行う方法を説明します。ビデオ&メディア
MediaQuery(今週のFlutterウィジェット)
https://www.youtube.com/watch?v=A3WrA4zAaPw
MediaQueryを使用して、さまざまな画面サイズに基づいてアプリのUIレイアウトを調整します。Flutter UI - クリーンデザイン - レンタルサービス
https://www.youtube.com/watch?v=y_hX5AAFEB8&feature=youtu.be
もう一つのUIチャレンジ。今回は、レンタルサービスのアプリです。Flutter :LinearGradientの背景
https://www.youtube.com/watch?v=FZiw9MWLlys
LinearGradientデコレーションを使用してウィジェット内に興味深い背景を作成する方法についてのビデオ。ソース生成とあなた自身のパッケージの書き方(The Boring Flutter Development Show、Ep。23)
ボーリングショーのこのエピソードでは、FilipはSwavとDiegoの2人のゲストが参加します。 Swavを使って、カスタムウィジェットを自動的に作るためのソース生成の使い方を紹介します。 Diegoでは、彼らはDiegoのパーセントインジケーターFlutterパッケージでCustomPainter関連の機能要求に取り組んでいます。Flutter Web:GithubページにFlutter UIKitをデプロイするピーナッツチュートリアル
https://www.youtube.com/watch?v=TJDSQBm51cI&feature=youtu.be
Flutter UI Kit WebアプリケーションをGitHubページまたはgithub.ioにデプロイする方法に関するチュートリアルFlutter Web:レスポンシブポートフォリオアプリケーションを作成するパート1
https://www.youtube.com/watch?v=QAHqlsAky_4&feature=youtu.be
Flutter For Webを使用してレスポンシブポートフォリオアプリを作成する方法プロバイダと接続性を使用したFlutterネットワークセンシティブUI
https://www.youtube.com/watch?v=u9O8NOnQi_A&feature=youtu.be
接続しているネットワークの種類に基づいてUIを更新するNetworkSensitiveウィジェットの作成方法を学びましょう。フラッタルートを使用したグローバル通知
https://www.youtube.com/watch?v=FRCvqkyeCzQ
より分離されたコードのためのルートミドルウェアと通知ミドルウェアを持つredux付きの本物のアプリ。ライブラリ&コード
appspector / flutter-plugin
https://github.com/appspector/flutter-plugin
AppSpectorをあなたのFlutterプロジェクトに統合するプラグイン。
pinkeshdarji / SuperHeroInteraction:スーパーヒーローインタラクション
https://github.com/pinkeshdarji/SuperHeroInteraction
Flutter開発したスーパーヒーローインタラクション
lesnitsky / network_state
https://github.com/lesnitsky/network_state
サービスアウェアネットワーク状態プラグイン
ayush221b / wallio
https://github.com/ayush221b/wallio
Android、iOS、Web、およびデスクトップ用のJSON解析およびアニメーションを実装する単純なフラッターアプリケーション。TakeoffAndroid /フラッター・サンプル
https://github.com/TakeoffAndroid/flutter-examples
キュレーションデザインの究極のチートブック
Ethiel97 / nice_button
https://github.com/Ethiel97/nice_button
あなたのアプリに手間をかけずに素敵なボタンをデザインするためのAndroidとIOS用のFlutterパッケージ。
kalismeras61 / flutter_web_dashboard
https://github.com/kalismeras61/flutter_web_dashboard
Flutterブートストラップスタイル管理UI
MeshackMusundi /国
https://github.com/MeshackMusundi/Countries
Countries GraphQL APIを使用して国のリストを表示するFlutterアプリ
andrewackerman / bloc_lite
https://github.com/andrewackerman/bloc_lite
合理化されたBLoC実装ライブラリ
evant / streamqflite
https://github.com/evant/streamqflite
sqlbriteに触発されたsqfliteの周りのFlutter反応型ストリームラッパー
メゾニ/マーシャリング
https://github.com/mezoni/marshalling
整列化ライブラリを使用すると、オブジェクトの整列化と非整列化(シリアル化/逆シリアル化も可能)を行うことができます(たとえば、json互換型への変換)。
roughike / streaming_shared_preferences
https://github.com/roughike/streaming_shared_preferences
Flutterプロジェクト用の反応型Key-Valueストア。 shared_preferencesと似ていますが、Streamsを使用します。
ayush221b / hack19ハンドブック
- 投稿日:2019-05-27T11:27:24+09:00
RxSwift初心者が公開したアプリの一部晒す
はじめに
アプリを使っていて、検索とかで表示レスポンスがリアルタイムなアプリとそうでないアプリがありました。
どうやってるのかなー?と調べてみるとリアクティブプログラミングなるものがあるらしい...
挙動としてはExcelとかがよく例えられます。
こちらの記事がイメージ掴みやすかったです。
リアクティブプログラミングへの理解がイマイチだったのでまとめてみた
で、iOSで実現するためのライブラリとしてはRxSwiftが他の言語にも流用しやすくて、おすすめらしい..
SwiftでReactive Programming
という記事を目にしてから数年立ってました。
今回勉強できる時間があったので、自作アプリをリリースがてらRxSwiftを使ってみて、
リアクティブプログラミングを実践してみようと思いました。今回リリースしたアプリをベースにして、一部機能を切り出してサンプルとして公開して、
やってみたことを備忘録として記事に起こそうと思います。元アプリ紹介
よくあるアプリですが、一見電卓に見えてパスコードを入力したら、
隠していた動画リストが出てくるというアプリです。結婚してムフフな動画も気楽に観れなくなったということもあり、同様のアプリを探してみたのですが、
使い勝手が悪かったり、昔に作られたままメンテがされてないアプリばかりだったので、
「ぼくがかんがえた さいきょう プライバシー動画ビューアーアプリ」として、
勉強がてら自作してみました。(結局このアプリが作ってることは嫁にバレている..)この手のアプリは「隠す」ことに重点が置かれていて、
動画のファイラーとしての使用感がよろしくないので、
その点についてこだわってみました(動画検索/圧縮機能あり)。現在は動画は写真アプリかファイルアプリから取り込むしかありませんが、
今後はurlから動画ダウンロード機能も追加していきたいと思います。(Apple的にNGかな?)生意気にも有料販売しているので、気が向いた方は是非ともお買い求めお願いします!
https://itunes.apple.com/jp/app/id1464652243目標
あんまり最初からハードル上げないよう
- MVVMアーキテクチャに触る
- RxSwiftを使ってViewControllerとViewModelのバインディングをやってみる
くらいにしました。
サンプル解説
元のアプリは
電卓画面 → パスワードを入れると動画ビューアー出現
というアプリでしたが、この中の電卓機能のみを切り出してサンプル作りました。
サンプルのリンクはこちら・処理の流れ(MVVMアーキテクチャ)
各クラスの役割
- CalculatorViewController(View)
ユーザーのインターフェース
ViewModelとデータバインディングし、自動描画する
→ボタンタップをViewModelに伝えて結果を更新
- CalculatorViewModel(ViewModel)
Viewのための状態保持
ViewからのデータをModelに伝達
→ViewのボタンタップをModel用に加工。結果をまた加工してViewに通知
- CalculatorModel(Model) ViewModelから受け取ったデータを処理する →実際の計算処理
View→ViewModel→Model
という参照/処理の流れで、
ViewはViewModelを参照するが、Modelを参照しない。
ViewModelはModelを参照するが、Viewは参照しない。
Modelは参照されるのみ。・RxSwiftのライブラリを使った部分
ViewとViewModelの表示のデータバインディング
今回表示系の更新は電卓の入力&計算結果を表示する
displayNumLabelのみ。
viewModelのBehaviorRelay変数displayNumと下記のようにバインディングさせる。
BehaviorRelayはacceptで値を変更するとき、onNextのイベントを受け取ることができる変数です。viewModel.displayNum .asObservable() .bind(to: displayNumLabel.rx.text) .disposed(by: disposeBag)ボタンタップのイベント処理
RxSwiftを利用しない時はタップ等のイベント処理で@IBActionを使用してましたが、
rx.tapを利用すれば@IBActionを利用しなくてもタップを検知し、処理を実行できるようになります。zeroButton.rx.tap .subscribe { [weak self] _ in guard let `self` = self else { return } // 処理を実行 self.setOperationButtonSelected(tapOperationButton: .none) self.viewModel.tapNumberButton(inputNum: "0") } .disposed(by: disposeBag)使ってみた感想
RxSwiftを使う前は
1. View入力受け取り 2. View入力受け取り値をViewModelへ伝達 3. ViewModelで処理 4. ViewModelの処理結果をViewへ伝達 5. View更新というプロセスを経なければならなかったですが、
RxSwiftを使えば、変数がView・ViewModelとバインディングされているので、
1. View入力受け取り 2. View入力受け取り値をViewModelへ伝達 3. ViewModelで処理(勝手にViewへ通知が行き、更新される)で済むようになりスッキリ書けるようになりました。
今後改善したいこと&疑問
・ ViewModelとModelのバインディング(?)
MVVMではRxSwiftなどのライブラリを用いてViewControllerとViewModelをバインディングするという記事をよく見かけるけど、
ViewModelとModelもバインディングするものなのかな??
計算機の部分のViewModelとModelだとシンプルだから必要ない気がする...参考にさせていただいた記事
上記以外の参考にさせていただいた記事を載せます。
オブザーバーパターンから始めるRxSwift入門
全体的な流れや概念の部分で参考にさせていただきました。RxSwift 再入門
イベント処理の流れについて参考にさせていただきました。
- 投稿日:2019-05-27T08:33:12+09:00
NativeBaseでタブ表示やリスト表示を試して雰囲気を掴む
NativeBaseとは?
ReactNative用のUIライブラリ
環境構築
yarnを使ってライブラリをインストール
$ yarn add native-base
実装
試しに、リスト表示+クリックした箇所をログ表示するまでをやってみます。
リスト表示
App.tsximport { Platform } from "react-native"; import { createAppContainer, createStackNavigator, NavigationContainer } from "react-navigation"; import Home from "./screens/home"; const headerNavigationOptions = { headerStyle: { backgroundColor: "gray", marginTop: Platform.OS === "android" ? 24 : 0 }, headerTitleStyle: { color: "white" }, headerTintColor: "white" }; const AppNavigator: NavigationContainer = createStackNavigator({ Home: { screen: Home, navigationOptions: { ...headerNavigationOptions, headerTitle: "サンプル" } } }); export default createAppContainer(AppNavigator);Home.tsximport { Container, Content, List, ListItem, Text, Body, Right, Icon } from "native-base"; import React from "react"; // tslint:disable-next-line: no-empty-interface interface IProps {} export default class Home extends React.Component<IProps> { public render() { // 表示するデータ const items: string[] = ["itemA", "itemB", "itemC"]; return ( <Container> <Content> <List dataArray={items} renderRow={item => ( <ListItem button={true} onPress={() => console.log(`onPress ${item}`)} > <Body> <Text>{item}</Text> </Body> <Right> <Icon name="arrow-forward" /> </Right> </ListItem> )} /> </Content> </Container> ); } }実行してiPhoneシュミレータで確認すると、以下の様な感じになりました
アイテムをクリックするとコンソールログに出力されます
クリップボードに長押ししたテキストを保存する
clipboard操作に関しては以下のモジュールを使用します。
ライブラリをインストール
$ yarn add @react-native-community/react-native-clipboard
clips.tsx... <Container> <Content> <List dataArray={items} renderRow={item => ( <ListItem button={true} onPress={() => console.log(`onPress ${item}`)} onLongPress={() => this.onLongClick(item)} > ... )} /> </Content> </Container> ... private onLongClick(item: string) { Clipboard.setString(item); Alert.alert("information", `「${item}」をクリップボードにコピーしました。`); } }これで長押しするとクリップボードに選択した文字列が設定されるようになりました
タブバーを下部に設置する
Footer Tabsを使用する
- まずはサンプル通りに実装
export default class Home extends React.Component<IProps> { public render() { return ( <Container> <Content /> <Footer> <FooterTab> <Button vertical={true}> <Icon name="clipboard" /> <Text>タブ1</Text> </Button> <Button vertical={true}> <Icon name="settings" /> <Text>タブ2</Text> </Button> </FooterTab> </Footer> </Container> ); } }表示だけは出来ました。
次はタブの切り替えや、現在表示されているタブが分かるようにしたいと思います。import { Body, Button, Container, Content, Footer, FooterTab, Icon, Text } from "native-base"; import React from "react"; import Tab1 from "./clips"; import Tab2 from "./settings"; interface IState { selectedTab: string; } // tslint:disable-next-line: no-empty-interface interface IProps {} export default class Home extends React.Component<IProps, IState> { constructor(props: IProps) { super(props); this.state = { selectedTab: "clips" }; } public renderSelectedTab() { switch (this.state.selectedTab) { case "clips": return <Tab1 />; case "settings": return <Tab2 />; default: } } public render() { return ( <Container> <Content>{this.renderSelectedTab()}</Content> <Footer> <FooterTab> <Button vertical={true} active={this.state.selectedTab === "clips"} onPress={() => this.setState({ selectedTab: "clips" })} > <Icon name="clipboard" /> <Text>タブ1</Text> </Button> <Button vertical={true} active={this.state.selectedTab === "settings"} onPress={() => this.setState({ selectedTab: "settings" })} > <Icon name="settings" /> <Text>タブ2</Text> </Button> </FooterTab> </Footer> </Container> ); } }
clips.tsx
とsettings.tsx
を別ページとして作成し、FooterTabのButton
が
押された時に state でどのページを表示するか管理しています。
Androidの場合
上のコードをAndroidで動かすと以下のようになりました。
タブバーの色等細かい所は調整が必要そうです
参考URL