- 投稿日:2019-10-11T21:20:46+09:00
ReactorKitでFirestoreをListenする方法
概要
ReactorKit
を使ったプロジェクトでFirestore
を使うことになったので、Pring
を用いて実装しました。環境
- Xcode 11.0
- Swift 5.1
- ReactorKit 1.2.1 (https://github.com/ReactorKit/ReactorKit)
- Pring 0.17.3 (https://github.com/1amageek/Pring)
実装
User
というモデルをFirestore
から取得してくるという設定です。Reactor側
Action
,Mutation
,State
,mutate
,reduce
はやるだけって感じです。
ポイントはdataSource
を以下のように用意して、private let dataSource: DataSource<User>?
setupDataSource()
内でDataSource
をlisten()
してやることです。
この時.on
の中でself?.action.onNext(.updateUsers(users))
することで、Firestore
から取得したデータをReactorのストリームにうまく取り込んでます。private func setupDataSource() { dataSource = User.query.dataSource() .on { [weak self] _, _ in guard let users = self?.dataSource?.documents else { return } self?.action.onNext(.updateUsers(users)) } .listen() }全実装は以下のようになります。
import ReactorKit import RxSwift import Pring final class UserListReactor: Reactor { enum Action { case updateUsers([User]) } enum Mutation { case setUsers([User]) } struct State { var users: [User] = [] } // MARK: - Variables let initialState = State() private let dataSource: DataSource<User>? // MARK: - Initializer init() { setupDataSource() } // MARK: - Setup Methods private func setupDataSource() { dataSource = User.query.dataSource() .on { [weak self] _, _ in guard let users = self?.dataSource?.documents else { return } self?.action.onNext(.updateUsers(users)) } .listen() } // MARK: - Reactor Methods func mutate(action: Action) -> Observable<Mutation> { switch action { case let .updateUsers(users): return .just(Mutation.setUsers(users)) } } func reduce(state: State, mutation: Mutation) -> State { var state = state switch mutation { case let .setUsers(users): state.users = users } return state } }ViewController側
いつも通り
bind(reactor:)
内でUserListReactor.State.users
をbind
してやれば終わりです。import UIKit import RxSwift import ReactorKit final class UserListViewController: UIViewController, View { // MARK: - Variables var disposeBag = DisposeBag() ... // MARK: - Bind Methods func bind(reactor: UserListReactor) { // State reactor.state.map { $0.users } .distinctUntilChanged() .bind { users in // tableView or collectionViewの更新 } .disposed(by: disposeBag) } }
- 投稿日:2019-10-11T20:32:27+09:00
Firebase めちゃ便利!Onekilo (iosアプリ)リリースしました!
Firebase めちゃ便利!! Onekiloリリースしました!
Outline
- 自己紹介
- Onekiloとは?
- Firebase のおすすめ点(もうみんな知ってるか笑)
- 最後に
自己紹介
初めまして!
立命館大学理工学部3年の葛上です!watnowの代表をしています!!
Twitterフォローしてください!Facebookも友達になってください!メンバー
ios 2人
UI/UXデザイナー 1人
動画作成 1人
の計4人で開発しました。ios 開発歴はまだ2年目で、今まで開発したサービスは5つほどしかありません。
Firebase に関しては、一年前にRealtimeDatabase 少し使ってtwitterみたいなのを使ったくらいです!Onekilo とは?
今回作成したサービスがOnekiloになります!
抽象的ではありますが、「今すぐ」何かするって当たり前になってきてるな
と思い、足掛かりとしてマッチングアプリを作りました。
概要としては 1km圏内の人とランダム通話ができると言う事です。
マッチング界はほとんどレッドオーシャン状態になっていますが、
すんごいニッチなターゲット選定をしています笑 そこが売りであり、僕たちの強みでもあり弱みでもあります笑
onekiloで使用したFirebaseの機能
(本音を言うとアプリに関係なく全て活用したかったです笑)
- Authentication
- ユーザー登録する際に用いました
- Cloud Firestore
- Database として使用しました
- Storage
- 画像の保存や引っ張てくるのに用いました。
- Remote Config
- 強制的にバージョンをアップデートを行うために用いました!(すんごい便利)
- Cloud Messaging
- ユーザーに通知を送るために使用しました。
- アナリティクス(全て)
- 実際にユーザーの反応を見るのにすごく便利です。
他にもML Kit とか使いたかったです。。。
上記の使い方などは要望があれば別で描こうと思います。
最後に
実は今年の4月くらいにOnekiloのアイデアが自分の頭の中ででていました。
こんなん誰も使わんやろと思いながら3ヶ月くらいすぎて、Cyber Agentやリクルートのインターンに参加させていただいた際にちゃんと事業戦略練ればいけるんじゃないかと期待してくれたのが今回僕が動く結果になりました。
ありがとうございます。
「マッチングサービスはレッドオーシャン」
「半径1kmのマッチングはニッチすぎる」と指摘されることは多々ありますが、自分の事業戦略を通して勝ち筋通りに進めたらいいなと思います。
もっと書きたいことはありますが、このような話はnoteに記しますので興味ある方は読んでください。。!最後にベータ版ではありますが、Onkilo のインストールをよろしくお願いいたします。
最後まで読んでいただきありがとうございます。
watnow
mememe
代表
葛上
- 投稿日:2019-10-11T15:55:00+09:00
iOSでハートのボタンアニメーションをつくる
先日「いいね!」的なハートのボタンアニメーションを作る機会がありまして。
先輩がライブラリ公開しているものを参考にしつつ、
https://qiita.com/darquro/items/c5cdf7dbcad1d5bb0188自分はライブラリではなく、自作で、
@IBDesignableと@IBInspectableを使って
ハートボタンとそのアニメーションを作ってみましたので、その備忘録。開発環境
Mac OS Mojave 10.14.6
Xcode 11.0
Swift 5つくりたいもの
- ハートの画像2枚をボタン押下で切り替える
- ボタン押下時(Highlight時)にハートをちょっと小さくする
- ボタン押下後(Touch up inside)にハートをちょっと大きくする
ハートの画像2枚をボタン押下で切り替える
アニメーションとは付加機能。
まずアニメーションなしで、つくりたいものを実現します。他にも方法はあると思いますが、今回はハートの画像2枚をボタン押下で切り替えます。
@IBDesignableと@IBInspectableを使った汎用的なボタン
@IBDesignableと@IBInspectableは非常に便利です。
(SwiftUIの世の波とは逆行してる感!)コードである程度の設定を書いておくと、
Storyboardでカスタマイズが容易になります。まずImageButtonという汎用的なボタンを設定します。
import UIKit @IBDesignable final class ImageButton: UIButton { @IBInspectable var unselectedImage: UIImage = UIImage() @IBInspectable var selectedImage: UIImage = UIImage() public var selectedStatus: Bool = false { didSet { setupImageView() } } override func awakeFromNib() { super.awakeFromNib() setupImageView() } override func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() setupImageView() setNeedsDisplay() } private func setupImageView() { self.setImage(self.selectedStatus ? self.selectedImage : self.unselectedImage, for: .normal) self.setImage(self.selectedStatus ? self.selectedImage : self.unselectedImage, for: .highlighted) } }
@IBInspectable
で指定した、unselectedImage
とselectedImage
は、
Storyboard上でカスタマイズできるようになります。Storyboardで設定
Storyboardでボタンを配置して、
上記のように、Custom Classに作成したImageButtonを設定すると、
このように、
unselectedImage
とselectedImage
に
好きな画像を設定できるようになります。つまりImageButtonはハートボタン以外にも利用できるので、便利感ありますね。
以下の画像を設定します。
- unselectedImage
- selectedImage
- DefaultのImage <- 忘れないように
アクションを設定
あとはボタンのクリック動作をハンドリングして、処理を書きます。
※Storyboardとコードを結びつけるとこは省略。@IBOutlet private weak var likeButton: ImageButton! @IBAction private func clickLikeButton(_ sender: Any) { likeButton.selectedStatus = !likeButton.selectedStatus }画像切り替え 完成
これでアニメーションのない、ハートボタンが完成です。
ハート押下時にアニメーションを
今回は
CASpringAnimation
を利用します。
ばねっぽいAnimationを表現しやすいと聞いたので。今回アニメーションを付けるのは2箇所あります。
- ボタン押下時(Highlight時)にハートをちょっと小さくする
- ボタン押下後(Touch up inside)にハートをちょっと大きくする
ボタン押下時(Highlight時)にハートをちょっと小さくする
方法は色々あると思いますが、自分は以下のコードで実現しました。
isHighlighted
でボタンが押されている状態かを識別します。override var isHighlighted: Bool { didSet { if isHighlighted { if imageView?.layer.animation(forKey: "reduced-size") == nil { let animation = CASpringAnimation(keyPath: "transform.scale") animation.duration = 0.05 // animation時間 animation.fromValue = 1.0 // animation前サイズ animation.toValue = 0.95 // animation後サイズ animation.mass = 0.1 // 質量 animation.autoreverses = false // 自動でfromの値に戻らない animation.initialVelocity = 40.0 // 初速度 animation.damping = 1.0 // 硬さ animation.stiffness = 40.0 // バネの弾性力 animation.isRemovedOnCompletion = false // animation動作後に完了状態としない animation.fillMode = .forwards // 一方向モード。fromの形状に戻らない imageView?.layer.add(animation, forKey: "reduced-size") } } } }ポイントは以下。
mass
initialVelocity
damping
stiffness
は動作を見てなんとなく値決め- 押下中にサイズが戻ってしまわないように、
autoreverses
isRemovedOnCompletion
はfalse、fillMode
は.forward
- animationは1回だけ実行にしたいので、animationが既に実行されているかどうかの判別式を記載
if imageView?.layer.animation(forKey: "reduced-size") == nil
- animation実行判別のために、任意のkey名
reduced-size
を設定これでボタン押下時のAnimationは実現できました。
しかし、これではimageView?.layer.add(animation, …)
で、追加された
animationがremoveされていません。それについては後述。ボタン押下後(Touch up inside)にハートをちょっと大きくする
こちらも方法は色々あるかと思いますが、今回は上記で設定した
setupImageView()
funcにanimationを記載することで実現しました。private func setupImageView() { self.setImage(self.selectedStatus ? self.selectedImage : self.unselectedImage, for: .normal) self.setImage(self.selectedStatus ? self.selectedImage : self.unselectedImage, for: .highlighted) let animation = CASpringAnimation(keyPath: "transform.scale") animation.duration = 0.3 // animation時間 animation.fromValue = 0.95 // animation前サイズ animation.toValue = 1.0 // animation前サイズ animation.mass = 0.6 // 質量 animation.initialVelocity = 40.0 // 初速度 animation.damping = 3.0 // 硬さ animation.stiffness = 40.0 // バネの弾性力 imageView?.layer.add(animation, forKey: nil) DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { self.imageView?.layer.removeAllAnimations() // highlightのAnimationも含め、removeする } }既述したもの以外のポイントは以下。
- animationが全て完了したらremoveする必要があるので、animation時間が経過したらremoveを実施
self.imageView?.layer.removeAllAnimations()
animationのremoveに関してはもっと良い方法があるかも...
指摘などあればぜひコメントお願いしますm(_ _)m完成
これでアニメーション付きのハートが完成しました。
まとめ
CASpringAnimationについて、
@IBDesignableと@IBInspectableについて、
詳細の説明は省略しました。参考資料をご確認頂ければと。参考
https://qiita.com/kaway/items/b9e85403a4d78c11f8df
http://www.cl9.info/entry/2018/05/26/175246
https://qiita.com/h-nag/items/7af47ea665332c3ac1bf
https://qiita.com/tasaiii725/items/c70bf648242b061e0734
https://qiita.com/son_s/items/7ca2acf690d10f9fd1b7
- 投稿日:2019-10-11T15:54:03+09:00
@IBDesignableと@IBInspectableで汎用的なButtonを
こんな感じのボタンを作りたいとき。
普通に
favoriteButton
として作成しても良いですが、
「他のボタンにも同じ構成で作れる汎用的なボタンにしたい」
という時に@IBDesignableと@IBInspectableが便利です。@IBDesignableと@IBInspectableとは
@IBDesignableとは?
@IBInspectableとは?
と聞かれるとなんて答えてよいか難しいのですが、@IBDesignableと@IBInspectableを使うと、
事前に作成しておいたViewを、Storyboard上で適用できる、カスタマイズできる
ものです。例を示します。
final class RoundedCornerButton: UIButton { @IBInspectable var iconImage: UIImage = UIImage() }このようにUIButtonを継承するRoundedCornerButtonクラスを定義して、
Storyboard上に設定したボタンのCustomクラスに、このようにRoundedCornerButtonを設定すると、
@IBInspectableで設定したiconImageをStoryboard上で設定できる、
というものです。RoundedCornerButton
細かく書くより、コード全体を載せたいと思います。
こんな感じのボタンを作成します。RoundedCornerButton:
import UIKit @IBDesignable final class RoundedCornerButton: UIButton { @IBInspectable var iconImage: UIImage = UIImage() @IBInspectable var unselectedText: String = "未選択" @IBInspectable var selectedText: String = "選択済み" @IBInspectable var unselectedBackgroundColor: UIColor = UIColor.systemTeal @IBInspectable var unselectedShadowColor: UIColor = UIColor.ocean @IBInspectable var unselectedBorderColor: UIColor = UIColor.clear @IBInspectable var selectedBackgroundColor: UIColor = UIColor.baseGray @IBInspectable var selectedShadowColor: UIColor = UIColor.gray @IBInspectable var selectedBorderColor: UIColor = UIColor.clear // ボタンがselectされているかどうかの変数 private(set) var selectedStatus: Bool = false // ボタンが凹む前のX座標 private lazy var originalX: CGFloat = { return self.layer.position.x }() // ボタンが凹む前のY座標 private lazy var originalY: CGFloat = { return self.layer.position.y }() private lazy var stackView: UIStackView = { let stackView = UIStackView(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height)) stackView.translatesAutoresizingMaskIntoConstraints = false // autoLayoutをONに stackView.alignment = .center stackView.distribution = .fill stackView.axis = .horizontal stackView.spacing = 4.0 stackView.backgroundColor = UIColor.clear stackView.isUserInteractionEnabled = false // stackView部分をタップしてもボタンが反映するように return stackView }() private lazy var iconImageView: UIImageView = { let iconImageView = UIImageView(image: iconImage) iconImageView.contentMode = .scaleAspectFit // 画像そのままに縦横比に表示 iconImageView.widthAnchor.constraint(equalToConstant: 32.0).isActive = true return iconImageView }() private lazy var textLabel: UILabel = { let width = stackView.frame.width - iconImageView.frame.width let textLabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: frame.height)) textLabel.text = unselectedText textLabel.textAlignment = .center textLabel.textColor = UIColor(hex: "#4A4A4A") textLabel.font = UIFont.boldSystemFont(ofSize: 16.0) return textLabel }() override func awakeFromNib() { super.awakeFromNib() setupLayer() setupViews() } override func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() setupLayer() setupViews() setNeedsDisplay() } // Highlight時にボタンが凹んだように見せるため、layerの位置変更・影なしに override var isHighlighted: Bool { didSet { layer.position = CGPoint(x: originalX, y: isHighlighted ? originalY+2.0 : originalY) layer.shadowOffset = isHighlighted ? CGSize(width: 0.0, height: 0.0) : CGSize(width: 0.0, height: 2.0) } } private func setupLayer() { setStatus(selectedStatus) layer.borderWidth = 1.0 // 枠線の長さを定義 layer.cornerRadius = 4.0 // 角丸に layer.shadowOpacity = 1.0 // 影を表示する layer.shadowRadius = 0.0 // ぼやけ影を非表示 } private func setupViews() { self.addSubview(stackView) // buttonのviewにstackViewを載せる stackView.addArrangedSubview(iconImageView) // iconImageViewをstackViewに載せる stackView.addArrangedSubview(textLabel) // textLabelをstackViewに載せる stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20.0).isActive = true // stackViewの左端をbuttonのviewの左端から20離す stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true } } // MARK: public extension RoundedCornerButton { func setStatus(_ status: Bool) { selectedStatus = status layer.backgroundColor = status ? selectedBackgroundColor.cgColor : unselectedBackgroundColor.cgColor layer.shadowColor = status ? selectedShadowColor.cgColor : unselectedShadowColor.cgColor layer.borderColor = status ? selectedBorderColor.cgColor : unselectedBorderColor.cgColor layer.shadowOffset = CGSize(width: 0.0, height: status ? 3.0 : 2.0) // 影の長さ textLabel.text = status ? selectedText : unselectedText } }ポイントは以下。
- レイアウトの配置は、Storyboardでエラーが出ないようにAutoLayout設定する感覚で。
- ボタンの上にViewを載せると、ボタンが押せなくなってしまうので、isUserInteractionEnabledをfalseに。
favoriteButtonにRoundedCornerButtonを適用
StoryboardでfavoriteButtonにRoundedCornerButtonをCustom Classに設定。
@IBInspectableで設けた項目をそれぞれ設定します。
favoriteButtonについて、コードで以下のようにアクションを設定。
@IBOutlet private weak var favoriteButton: RoundedCornerButton! @IBAction private func clickFavoriteButton(_ sender: Any) { favoriteButton.setStatus(!favoriteButton.selectedStatus) }以上で完成です。
まとめ
StackViewを用いている@IBDesignableと@IBInspectableの例が
あまりネット記事になかったこともあり、記事として記載してみました。ぜひ参考にしてみてください。参考
- https://developer.apple.com/library/archive/referencelibrary/GettingStarted/DevelopiOSAppsSwift/ImplementingACustomControl.html
- https://qiita.com/son_s/items/7ca2acf690d10f9fd1b7
- https://qiita.com/tasaiii725/items/c70bf648242b061e0734
- https://qiita.com/dddisk/items/8001598ea7951bcdcc30
- https://qiita.com/yucovin/items/4bebcc7a8b1088b374c9
- https://qiita.com/ladnack/items/149b4ebca7f4f45e5335
- http://kimagureneet.hatenablog.com/entry/2015/11/27/211353
- https://qiita.com/kuninori/items/7c4fbd9840c6dd07fed7
- https://qiita.com/yucovin/items/ff58fcbd60ca81de77cb#stackviewをコードで作る方法
- https://qiita.com/ika_tarou/items/e411c37b064fdd514afb
- https://qiita.com/stastaahaha/items/e45e7559255fb7666fd8
- 投稿日:2019-10-11T14:49:02+09:00
[saml2aws] error authenticating to IdP: page is missing saml assertion
問題
いつも使っている
aws-auth
でAWSにログインしようとすると、以下のエラーが返してきた。error authenticating to IdP: page is missing saml assertion背景
- MacOS 10.14.6
対応
brew upgrade saml2aws原因
認証画面が変わった気がします。ちなみに、iOSだと発生しますが、Androidは発生してないようです。
aws-auth
一発ログイン
saml2aws login -a idp_account_name —username=username --password=‘password' --role='arn:aws:iam::31xxxxx:role/Administrator' --skip-prompt
- 投稿日:2019-10-11T12:44:48+09:00
ios13のWKWebViewのWebRTC対応状況まとめ
- 投稿日:2019-10-11T12:38:26+09:00
iOS13からAirDrop痴漢ができなくなった。Appleの痴漢対策か!?
以前、AirDrop痴漢に気をつけましょうという記事を書きましたが
https://qiita.com/a-nishimura/items/b1f709126227a4876146なんとiOS13からAirDropの仕様変更がありました。
画像を受け入れるまでプレビューされなくなりました。ということで、わいせつな画像を送りつける「AirDrop痴漢」ができなくなったのです。
これはまさかAppleのAirDrop痴漢対策なのでしょうか。
はたまたただの仕様変更なのでしょうか。
理由は定かではないのですが、犯罪に使われるのもよくないのでコンプライアンス的な背景もあったのかもしれませんね。ただ、プレビュー機能自体はとても便利なものだったので、
その便利なものを悪用する人がいて、仕様が変わっていくのは少し残念ですね。うちのブログでも書いているのでよかったらぜひご覧ください
https://www.wow-creators.com/2019/07/12/【まとめ】airdrop痴漢やってみた/
- 投稿日:2019-10-11T11:00:01+09:00
Objective-CでprivateメソッドのUnit Testの作り方
普段開発時に、自分が作ったメソッドに対して、Unit Testで動作確認したいけど、
privateメソッドは他のクラスからアクセスできない、Unit Testを動くために、一時的ヘッダークラスに定義するのも微妙です。Objective-Cのカテゴリを利用することで他のクラスからprivateメソッドへのアクセスも可能です。
テストしたいダミークラスSomeClassを用意する
SomeClass.h
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface SomeClass : NSObject @end NS_ASSUME_NONNULL_END今回はヘッダーファイルに特に何も定義していない。
SomeClass.m
#import "SomeClass.h" #import <os/log.h> @implementation SomeClass - (BOOL)privateMethod { os_log(OS_LOG_DEFAULT, "This is a private method"); return YES; } @end実装ファイルにYESを返すメソッドを1つ用意します。
コンソールにメッセージを1行出力します。Unit Testのクラスを作る
新規作成からUnit Test Case Classを選択します。
クラス名はわかりやすいように、今回はUTT_SomeClassTestにします。
※ここからは重要
Unit Testクラスの一番最初にテストしたいクラスのカテゴリを作ります。
カテゴリの中にテストしたいprivateメソッドを定義します。@interface SomeClass (Test) - (BOOL)privateMethod; @endSomeClassはテストしたいクラスのクラス名、括弧に定義した「Test」はテスト用のカテゴリを明記するため、他のカテゴリも問題ないです。
Unit Testの全文は下記のようになります。
#import <XCTest/XCTest.h> #import "SomeClass.h" @interface SomeClass (Test) - (BOOL)privateMethod; @end @interface UTT_SomeClassTest : XCTestCase @end @implementation UTT_SomeClassTest - (void)setUp { // Put setup code here. This method is called before the invocation of each test method in the class. [super setUp]; } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testPrivateMethod { SomeClass *someClass = [[SomeClass alloc] init]; XCTAssertTrue([someClass privateMethod]); } @end最後、テストを実行する
実施結果は問題なく通りました。
コンソール上にもメッセージが出力されています。
Test Suite 'Selected tests' started at 2019-10-11 10:19:26.658 Test Suite 'Unit Test TestTests.xctest' started at 2019-10-11 10:19:26.659 Test Suite 'UTT_SomeClassTest' started at 2019-10-11 10:19:26.659 Test Case '-[UTT_SomeClassTest testPrivateMethod]' started. 2019-10-11 10:19:26.671954+0900 Unit Test Test[10250:2834238] This is a private method Test Case '-[UTT_SomeClassTest testPrivateMethod]' passed (0.013 seconds).テストクラスからもprivateメソッドのアクセスも可能になりました!
参考サイト
https://stackoverflow.com/questions/18354788/unit-testing-private-method-objective-cThanks Abizern, saved my day.
- 投稿日:2019-10-11T00:21:45+09:00
[Xcode11]StoryboardでSuperViewへのEqualWidth,EqualHeightができなくなった時の解決法
はじめに
Xcode11でstoryboardを使っていて、ViewをSuperViewと同じ横幅にしようと思ったのですが、ViewをSuperViewへドラッグしたところEqualWidthsは見当たりませんでした、
問題
Xcode11以前はStoryboardでViewなどをドラッグしてSuperViewに合わせると
このようなメニューが出てきて、EqualWidthsを選択するとEqual Widths Constraintをつけることができました
しかし、Xcode11では同じ操作をするとこのような画面になり、メニューからEqualWidths,EqualHeights,Aspect Ratioが消えてしまいました。
回避策
先ほどSuperViewへドラッグした操作をSafeAreaに変更すると、EqualWidths,EqualHeights,Aspect Ratioが出現するので選択します。
そうするとこのような感じになりますので、Second Itemの欄からSuperViewを選択してあげると
SuperViewと同じ高さにしてあげることができました
参考リンク