- 投稿日:2019-12-04T23:10:13+09:00
[Swift] Connectionの概要・使い方まとめ
自己紹介的な
Swift歴2日目の超初級者。
本記事では学習したことをアウトプットして、自分の中で定着させることを目的としています。
初学者のスタートダッシュの手助けになれば幸いです。Connectionについて
UI部品とコードを関連付ける際に、コード上でUI部品にどのような役割を持たせるか指定することができる。
具体的には、ConnectionにOutletかActionを設定することで指定できる。OutletとActionって何ぞや?
OutletとActionはどのように使い分ける?
色々調べたことをまとめると、
・Outlet → そのUI部品をどこかで参照したいとき
・Action → そのUI部品の動作内容を決めたいとき例えば、buttonを押したらlabelに"Hello World"が出力される場合を考える。
buttonはlabelを参照して"Hello World"を代入する、つまりUI部品の動作内容を決めているのでAction。
labelは、buttonから参照されているのでOutlet。おわりに
もし間違っていたらご指摘頂けると助かります。。。
- 投稿日:2019-12-04T22:54:52+09:00
UIViewControllerのアニメーション無しpresent遷移
はじめに
普段、IOSのアプリ開発をしているゆーすけと言います。
これから備忘録も兼ねてQiitaの記事を投稿していこうと思います!
あまり投稿は慣れてないので多少見辛いかもしれませんが
よろしくお願いします。UIViewControllerのアニメーション無しpresent遷移
現在新規アプリの開発に携わっているのですが、
アニメーション無しでViewControllerの遷移をする必要がありました。
今までアニメーション無しの遷移なんてなかなか使ってなかったので
直ぐには方法が思いつきませんでした。実装
FirstViewController.swiftlet secondViewController = UIViewController() self.present(secondViewController, animated: false, completion: nil)ポイントはanimatedをfalseにすることです。
falseにすることでアニメーション無しで遷移できます。普段、何も意識せずanimatedはtrueにしていたので、
それぞれの引数がどんな役割をしているのか、もっと意識して実装しないとなと
つくづく感じました。。
- 投稿日:2019-12-04T22:20:55+09:00
FirestoreのArrayContainAnyクエリでタイムラインを実装してみた
はじめに
初アドベントカレンダー!!
普段はiOS開発のインターンをしながら、個人でコツコツアプリを作ったりしております。
今回はFirestoreのアップデートでArrayContainAnyというQueryが追加され、どうやらタイムライン実装で活躍してくれそうで、自分なりにやってみたので記事にしてみました。
※ 間違えている点や、改善点などございましたらコメントかTwitter(https://twitter.com/yuto_nakano44)
にご連絡いただければ幸いですArrayContainAnyクエリとは
こちらの記事がとても分かりやすかったので詳しくはこちらをみていただけると!↓
https://medium.com/anti-pattern-engineering/待ちに待ったfirestoreにarray-contains-anyとin検索がキタ-1771cbc14724使い方としては、newsコレクションのsports配列にbasketBallが要素として含まれているドキュメントが欲しい!
となれば下記のようになります↓collection("news").whereField("sports", arrayContainsAny: ["basketball"])basketballだけでなく、baseballが要素として含まれているドキュメントが欲しい!
となれば下記のようになります↓collection("news").whereField("sports", arrayContainsAny: ["basketBall", "baseball"])タイムライン機能実装
現在、個人でアプリを作っており、その過程で以前シンプルなタイムライン機能を実装しました。
タイムラインを構成するデータとしては下記の通りです。
・users (ユーザーのプロフィール情報が保存されているコレクション)
・posts (全ユーザーの投稿情報が保存されているコレクション)実装方法
コレクションのデータです↓
postsコレクション
posts - id - title - content - reply_countusersコレクション
users - id - name - user_icon - posts <- userが投稿したpostを格納した配列手順としては
1, postsを取得
2, posts一つ一つのidを配列に格納
3, arrayContainsAnyクエリでpostのidにヒットしたusersのドキュメントを取得コード
func fetchUser(posts: [Post], completion: @escaping ((Result<[User], Error>) -> Void)) { // postのidを配列に格納 let postIds = posts.map { (post) -> String in return post.id } // usersコレクションのposts配列にpostIds内のidが含まれていたら Firestore.firestore().collection("users").whereField("posts", arrayContainsAny: postIds) .addSnapshotListener({ (snapshot, error) in guard let snapshot = snapshot else { return } let users = snapshot.documents.map { (user) -> User in return try! Firestore.Decoder().decode(User.self, from: user.data()) } completion(.success(users)) }) }ユーザー取得できた?
※クエリの配列(今回で言うpostIds)には10個の要素までしか配列に格納できず、
10個より多いと以下のエラーが出てクラッシュします。↓"Invalid Query. 'arrayContainsAny' filters support a maximum of 10 elements in the value array."配列の要素が空の場合も↓
Invalid Query. A non-empty array is required for 'arrayContainsAny' filters.ログでしっかりエラーの原因を出してくれるとありがたいですね。
今回の方法でタイムラインを実装するなら10件までしか一度に表示できないですね。
必要なタイミングで都度データを取得する必要がありそうです。終わりに
自分なりにタイムラインの実装をやってみたので記事にしてみました。
Firestoreのデータ構造について勉強しなければ。
フィードバックいただけると嬉しいです!
ありがとうございました。参考: https://medium.com/anti-pattern-engineering/待ちに待ったfirestoreにarray-contains-anyとin検索がキタ-1771cbc14724
- 投稿日:2019-12-04T21:24:07+09:00
Swift Network.framework Study 20191204「UDP」
Study
Network.framework
Study:Client側環境
Client:Swift、Xcode
Server:Java、NetBeansClient Source Swift
SwiftはNWConnectionに「NWParameters.udp」を設定するだけ。
import Foundation import Network var running = true func startConnection() { let myQueue = DispatchQueue(label: "ExampleNetwork") let connection = NWConnection(host: "localhost", port: 7777, using: NWParameters.udp) connection.stateUpdateHandler = { (newState) in switch(newState) { case .ready: print("ready") sendMessage(connection) case .waiting(let error): print("waiting") print(error) case .failed(let error): print("failed") print(error) default: print("defaults") break } } connection.start(queue: myQueue) } func sendMessage(_ connection: NWConnection) { let data = "Example Send Data".data(using: .utf8) let completion = NWConnection.SendCompletion.contentProcessed { (error: NWError?) in print("送信完了") running = false } connection.send(content: data, completion: completion) } startConnection() //dispatchMain() while running { sleep(1) }Server Source Java
JavaはTCPとUPDで使用するクラスが違う。
package example.java.network; import java.net.DatagramPacket; import java.net.DatagramSocket; public class ExampleServerUDP { private static final int EXHOMAX = 255; public static void main(String[] args) { try { DatagramSocket datagramSocket = new DatagramSocket(7777); DatagramPacket datagramPacket = new DatagramPacket(new byte[EXHOMAX], EXHOMAX); while(true) { datagramSocket.receive(datagramPacket); for(int i = 0; i < datagramPacket.getLength(); i++) { System.out.print(datagramPacket.getData()[i]); System.out.print(" "); } System.out.println(); datagramPacket.setLength(EXHOMAX); } } catch(Exception e) { } } }
- 投稿日:2019-12-04T21:03:55+09:00
Makefileを利用してiOS開発を賢く便利に運用しよう?
はじめに
VALU Advent Calendar 2019 4日目の記事です!
VALU 社として記事を書きたい! と名乗りを上げて2年目になりました。ことしもよろしくおねがいします!今回は「Makefile を利用して iOS 開発を賢く運用しよう」ということで,
Makefile
の補完機能を用いまして,チームメンバーが快適に,かつ本質に集中できる環境を維持することのできる試みのご紹介です。目次
- Makefile との出会い
- Makefile によるスクリプト管理は何が嬉しいのか
- iOS で実際に利用している Makefile の中身
- Makefile に置くべき便利なワンライナー
- おわりに
- References
Makefile との出会い
初めて Makefile と出会ったのは,そうですね。もともとは組み込み寄りであった自身の弱点を克服するため,gohome という Go 製の API サーバーを作成していた最中の出来事でした。
Go 言語自体も初めてであったことで色々調べているうちに Golang を使うなら Makefile を恐れるな という記事に出会いました。
本来の Makefile の使い方とは異なりますが,
Makefile
を作成し,よく使うコマンドを:
区切りの Key とスクリプトで構成する。ターミナルから$ make build
などと入力してスクリプトを実行させると便利だよ,という記事です。Makefile の例。画像は YutoMizutani/gohome/Makefile から一部を抜粋
わたしはこれを読み大変感動しまして,ぜひ iOS にも導入しようと決意しました。
Makefile によるスクリプト管理は何が嬉しいのか
環境構築を一括で管理できる
Makefile を利用することで,個々のコマンド操作を統制することが可能です。
最も恩恵を得られるのは新しい人が参入した際の環境構築だと思います。あなたの
README.md
はどうでしょうか?「CocoaPods のインストール方法は...」等の長い文章で書かれていませんか? 利用されているツールしか書かれていない場合もあるかと思います。
新しい開発者の目的は,「そのリポジトリがどう動いているかを知ること」より「目的の機能または問題に着手すること」です。フルスタックエンジニアが助けに来たとしても,1日を環境構築で終えるような組織ではとてももったいないですよね。
VALU では
$ make all
のみで環境構築が完了します補完が効く
zsh-completions などの補完用プラグインを用いることで,どんなコマンドがあるかを実行前に確認することができます。
これが ShellScript だけでなく Makefile を利用する利点です。Branch 間の環境変化にも常に同じコマンドで対応できる
「今回新しく CocoaPods を Homebrew から Gem を利用するように変えました! 以降開発者はこのコマンドを利用してください! 古いコマンドは使わないでください!」
という初心者が泣いてしまう呪文を
Makefile
は解決します。
Makefile
内にスクリプトを書いてコマンドとスクリプトを分離させることで変更に強くなります。iOS 風に言うとprotocol
経由でアクセスする,と言えばイメージがしやすいですかね。呼ぶ側は常に
make
コマンドから。$ make install
と呼ぶことで環境が変わってもインストール作業が継続できます。自動環境構築の肝,自動インストール
Homebrew がない? インストールします。 CocoaPods ももちろん。Ruby のバージョンが違う? rbenv を利用するように変更します,というように存在しない場合にも自動でインストールさせるようにしています。
最近「おま環」という言葉を知りました。OSS だとそうもいきませんが,会社PCの社内プロジェクトなので,必要なものは自動で構築させる方が効率的です。
以下は Xcode のバージョンを指定するスクリプトです。CocoaPods 等の Ruby 製ツールは,VALU では Gemfile を用いて管理しています。
各スクリプトは,そのコマンドの実行前にtype
gem contents
mint list | grep -s
等を用いて目的のコマンドが存在するかを判定し,存在しない場合にインストールスクリプトが走るようにしています。
$(dirname $0)/../gems/bundle-install.sh
の先では,bundle
コマンドが存在するかを判定しており,必要に応じて Ruby 自体がインストールされているかまで遡り,対応が必要なPCにのみ適切にインストールが走るようになっています。このような綺麗なスクリプトも
$ sh select-xcode-version.sh
と長ければ全メンバーに実行してもらえない可能性があります。sh
って打てば補完されるだろう,というのは「おま環」でした。
make
にコマンドを集めておき,短いコマンドで呼び出せるようにしておくことでスクリプトとの距離が縮まります! すてきです!select-xcode-version.sh#!/bin/sh # Install Gems if !(gem contents xcode-install > /dev/null 2>&1); then sh $(dirname $0)/../gems/bundle-install.sh fi # Check argv if [ $# -ne 1 ]; then echo "select-xcode-version.sh: error: Required the version" 1>&2 exit 1 fi # xcode-install says `Xcode VER.SION.NUM.BER` XCVERSION_OUTPUT_PREFIX="Xcode " EXPECT_XCODE_VERSION=$1 CURRENT_XCODE_VERSION=`xcversion selected | grep -i $XCVERSION_OUTPUT_PREFIX | tr -d $XCVERSION_OUTPUT_PREFIX` # Select Xcode version if these are different if test $EXPECT_XCODE_VERSION != $CURRENT_XCODE_VERSION; then bundle exec xcversion install $EXPECT_XCODE_VERSION ; : bundle exec xcversion select $EXPECT_XCODE_VERSION fiiOS で実際に利用している Makefile の中身
Swift も LLVM を利用している言語ですが,Xcode によってコンパイル周りのオプションは肩代わりすることができています。
Swift 自体のビルドに必要なすごくながいコマンド とは異なり,とてもシンプルに記述することができます。
あくまでMakefile
を使おうというものなので,おまじないや固有表現を避け,ShellScript が読める人なら内容が読めるような形に抑えています。
加えて,具体的なコマンドは環境に依存するパスおよび拡張子以外は別の ShellScript としてファイルを分けており,CI 時に必要なスクリプトは個々に実行できる環境を作り上げています。こちらの Makefile は「人間がコマンドを覚えず,内容を知らなくとも実現したいことを実現する」という方針で設計しており,
利用者は「とりあえず$ make
を打ってみて,補完からデプロイやビルドなどのしたいことを探す」
成長したい人は「Makefile を覗いてみて,必要に応じて質問や修正,追加を行う」
ことで業務の効率化を実現しています。これにより,QA チームへの環境構築や作業依頼についても「
$ make clean
をしてみてください」と Slack や口頭で伝達することができるようになっていますMakefile# Paths PROJECT_PATH=./ # File extensions PROJECT_EXTENSION=.xcodeproj WORKSPACE_EXTENSION=.xcworkspace # Definition PROJECT_NAME=`find $(PROJECT_PATH) -maxdepth 1 -mindepth 1 -iname "*$(PROJECT_EXTENSION)" | xargs -L 1 -I {} basename "{}" $(PROJECT_EXTENSION)` PROJECT=$(PROJECT_PATH)$(PROJECT_NAME)$(PROJECT_EXTENSION) WORKSPACE=$(PROJECT_PATH)$(PROJECT_NAME)$(WORKSPACE_EXTENSION) XCODE_VERSION=`cat $(PROJECT_PATH).xcode-version` # Xcode を開く open: sh scripts/general/xcode/open-xcode.sh $(WORKSPACE) # Xcode を強制終了させる kill: sh scripts/general/xcode/kill-xcode.sh # 一括で環境構築を行う all: make config make select make generate make install make sort make clean # ~~~ 省略 ~~~ # ビルドに必要なファイルを生成する generate: make generate-xcodeproj generate-xcodeproj: sh scripts/general/xcodegen/generate-xcodeproj.sh # 依存するツールやライブラリをインストールする install: make install-mint make install-gems make install-pods install-mint: sh scripts/general/mint/mint-install.sh install-gems: sh scripts/general/gems/bundle-install.sh install-pods: sh scripts/general/cocoapods/pod-install.sh余談ですが,この
$ make all
内では複雑なスクリプトを隠蔽しています。
上記のmake config
内では,post-checkout
の Git hook に紐づけてインストール (bootstrap) を入れています。
発生し得るトラブルには事前に対応しましょう。
「ブランチを変えたら (ライブラリのバージョンが変わったために) ビルドできなくなった」という苦言に毎回対応するのも,そのためのドキュメントを保守するのもつらいです。
Makefile に置くべき便利なワンライナー
この程度,
.bashrc
に書いておけという意見も分かります。
一方で,リポジトリで共有するということは「あの人の便利な技」チーム全体がその commit から便利になるということです。
何を書いたら良いか分からないという方へ! まずこれを入れて幸せになりましょう!ディレクトリ配下の Xcode workspace を検索して起動する
open
コマンドは macOS で default で指定されたアプリを用いて起動させるコマンドです。実際にはプロジェクトディレクトリやxcworkspace
の拡張子を変数に切り出していますが,xcworkspace
を利用していることが分かっている場合は,より短いコマンドでターミナルから Xcode を開くことが可能です。ターミナル$ open `find . -maxdepth 1 -mindepth 1 -iname "*.xcworkspace"`
-a
オプションによって指定したアプリを用いて開くことも可能です。複数の Xcode をインストールしている場合,xcode-select
で指定されている PATH を利用して起動させることもできます。PROJECT_PATH=`find . -maxdepth 1 -mindepth 1 -iname "*.xcworkspace"` XCODE=`xcode-select --print-path | awk 'match($0, /^.*.app/){ print substr($0, RSTART, RLENGTH) }'` open -a $XCODE $PROJECT_PATHキャッシュと Derived Data の削除
よく使うキャッシュと Derived Data の削除を行うワンライナーです。
; :
と最後に付けることでrm
時に Fail しないようにしています。
われわれは記憶のテストをしているわけではないので DerivedData の場所 (~/Library/Developer/Xcode/DerivedData
) は暗記するよりどこかに書いておきましょう。ターミナル$ xcodebuild -alltargets clean ; rm -rf ~/Library/Developer/Xcode/DerivedData/ ; :Xcode project をソートする
有名な Xcode のソートスクリプト をありがたく利用させていただくワンライナーです。Perl ファイルなので
curl
でお借りして使い終わったら破棄するだけです。
Makefile
内に書いておくことで,常に綺麗な状態で開発を始めることができて便利です。ターミナル$ curl -sS https://raw.githubusercontent.com/WebKit/webkit/master/Tools/Scripts/sort-Xcode-project-file \ > ./script.pl \ && perl ./script.pl `find . -maxdepth 1 -mindepth 1 -iname "*.xcodeproj"` \ && rm -f ./script.plおわりに
これらは別のリポジトリとして GitHub に公開した方が良いのでは?という声もありました (失念しましたが,実際別リポジトリからスクリプトを叩けるものを最近見かけましたね)。
一方で,ブランチを分けることによって考慮しなければならない (e.g. Xcode のバージョン更新時に一部スクリプトは利用できるが,これは巻き戻さないといけない) 等の問題が発生するためにやめました。Makefile およびスクリプトを一人で書いているので......Alfred を利用してあれこれする便利ですが,簡単な操作はターミナルで完結させるのが良いです。ターミナルって結構便利なんですよ,ということが少しでも伝われば幸いです。難しいところは強い人にやってもらって,その恩恵を慣れていない人にも受けられるようにする。みんながハッピーになるといいな。
VALU Advent Calendar 2019,明日は弊社 CTO @mito_memel さんのすてきなサーバーサイドのお話です。
References
- YutoMizutani/gohome - GitHub
- Golang を使うなら Makefile を恐れるな
- macOS用パッケージマネージャー — Homebrew
- CocoaPods
- rbenv/rbenv - GitHub
- おま環」とは?意味や使い方を元ネタを含めてご紹介 - コトバの意味辞典
- Carthage/Carthage - GitHub
- Alfred - Productivity App for macOS
- zsh-users/zsh-completions
- apple/swift/CMakeLists.txt - GitHub
- WebKit/webkit/Tools/Scripts/sort-Xcode-project-file
- 投稿日:2019-12-04T20:50:59+09:00
Xcode11.1のローカライズではめられた!
はじめに
Xcode10系から11系にあげてしばらくしてから下記のような画面を見つけた。
??日本語と英語混じってる
現象
なんか同じ画面なのに一部だけローカライズできていない。
理想は下記のような表示。
日本語版 英語版 Storyboardのローカライズがおかしい?ローカライズファイルは下記のようなもの
Main.strings(Japanese)/* Class = "UILabel"; text = "かすたむ"; ObjectID = "3Iv-wk-3sA"; */ "3Iv-wk-3sA.text" = "かすたむ"; /* Class = "UINavigationItem"; title = "テスト"; ObjectID = "7pt-6w-M5O"; */ "7pt-6w-M5O.title" = "テスト"; /* Class = "UILabel"; text = "でぃてぃーる"; ObjectID = "DV3-DC-Qdc"; */ "DV3-DC-Qdc.text" = "でぃてぃーる"; . . . /* Class = "UILabel"; text = "フッター"; ObjectID = "vyK-5m-pLY"; */ "vyK-5m-pLY.text" = "フッター";Main.strings(English)/* Class = "UILabel"; text = "かすたむ"; ObjectID = "3Iv-wk-3sA"; */ "3Iv-wk-3sA.text" = "Custom"; /* Class = "UINavigationItem"; title = "テスト"; ObjectID = "7pt-6w-M5O"; */ "7pt-6w-M5O.title" = "Test"; /* Class = "UILabel"; text = "でぃてぃーる"; ObjectID = "DV3-DC-Qdc"; */ "DV3-DC-Qdc.text" = "Detail"; . . . /* Class = "UILabel"; text = "フッター"; ObjectID = "vyK-5m-pLY"; */ "vyK-5m-pLY.text" = "Footer";なんか
static cell
の画面だけおかしい調査
パーツのObjectIDを確認
よくわからないけどなんかのタイミングで
ObjectID
変わったのかな?と思い確認してみました。とくに問題なし
ローカライズファイルを入れ直してみる
よくわからないけどとりあえずローカライズのチェックを入れ直してファイルを再生成してみました。
変化なし
Xcodeのリリースノートを確認
しばらく悩んでXcodeのリリースノートをみてみました。
発見
UITableViewCell labels in storyboards and XIB files do not use localized string values from the strings file at runtime. (52839404)
どうやら
UITableViewCell
のlabel
がローカライズできないようです。
TableView
のContent
をStatic Cells
にしてCell
のStyle
が下記いずれかの場合に起こるようです。
- Basic
- Right Detail
- Left Detail
- Subtitle
(最初のスクショは上から Basic, Right Detail, Left Detail, Subtitle, Custom のセルです)
対応
Xcodeのバグなのでリリースを待てばいいのですが、アプリの提出期限もあり下記のような暫定対応をしました。
1 TMPLocalizable.strings ファイルを作る
TMPLocalizable.strings(Japanese)"かすたむ" = "かすたむ"; "テスト" = "テスト"; "でぃてぃーる" = "でぃてぃーる"; . . . "フッター" = "フッター";TMPLocalizable.strings(English)"かすたむ" = "Custom"; "テスト" = "Test"; "でぃてぃーる" = "Detail"; . . . "フッター" = "Footer";2
ViewController
にローカライズ処理を書く// FIXME: Xcodeのバグがなおったら消したい override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = super.tableView(tableView, cellForRowAt: indexPath) if let text = cell.textLabel?.text { cell.textLabel?.text = tmpLocalizedString(text) } if let text = cell.detailTextLabel?.text { cell.detailTextLabel?.text = tmpLocalizedString(text) } return cell } private func tmpLocalizedString(_ key: String) -> String { return NSLocalizedString(key, tableName: "TMPLocalizable", comment: "") }これで表示は想定通りになります。
さいごに
上記のような対応で無事ローカライズ対応することができました!!!が、対応後すぐにバグが修正されたXcode11.2がリリースされました...
私はそっと
revert
しました
- 投稿日:2019-12-04T20:26:57+09:00
【Swift】Cell上のUIImageViewでAPIから取得した画像(URL)を表示させたこと
概要
Cell上のUIImageViewにAPIから取得した画像(URL)を表示するということが結構難しかったのでメモします。
※URLSessionを使用します。作業の順序
1.APIから取得したURLを使ってサーバーから画像を取得
2.imageviewに取得した画像をセットする
実装
SongTableViewCell.swiftclass SongTableViewCell: UITableViewCell { @IBOutlet weak var trackImage: UIImageView! override func awakeFromNib() { super.awakeFromNib() } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } //"Result", "artworkUrl100"はAPI実装のファイルから取得している値 func setup(set: Result) { //2:imageviewに取得した画像をセットする trackImage.setImageByDefault(with: set.artworkUrl100) } } //1:APIから取得したURLを使ってサーバーから画像を取得 extension UIImageView { func setImageByDefault(with url: URL) { URLSession.shared.dataTask(with: url) { [weak self] data, response, error in if error == nil, case .some(let result) = data, let image = UIImage(data: result) { //ここでメインスレッド実行するように記述(無いとサブスレッドで実行されて落ちる) DispatchQueue.main.async { self?.image = image } } else { // error handling } }.resume() } }ListTableViewController.swiftoverride func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "SongTableViewCell") as? SongTableViewCell else { return SongTableViewCell() } let info = resultsCount?.results?[indexPath.row] else { return SongTableViewCell() } cell.setup(set: info) return cell }ひとまず画像は取得できるようになりました。
終わりに
以下の記事などを参考にして実装しました。
https://qiita.com/H_Crane/items/422811dfc18ae919f8a4
https://qiita.com/nbapps_dev/items/d22837b04d8bb0a3127d
- 投稿日:2019-12-04T18:44:11+09:00
HealthKitで水泳の各ラップのストローク数が取りたい
はじめに
Apple Watchで水泳のワークアウトが測定できるの知ってました?
ワークアウトアプリにプールスイミングがあるので、これを実行しながらプールで泳げば、測定できます。
測定結果はApple WatchとペアリングしているiPhoneのアクティビティアプリで確認できます。
サンプルデータですが、このように表示されます。
ラップ数や泳いでいるときの泳法も測定できていて、すごい! てなるのですが、
このアプリでは各ラップのストローク数は確認できません。
HealthKitで取れるだろう、と思ってやってみました。アプリでHealthKitが使えるようにする
公式ドキュメントを参照して、手順通りに行います。
プロジェクトファイルとInfo.plistを編集します。水泳のワークアウトデータを取ってみる
サンプルコード
import HealthKit let healthStore = HKHealthStore() let readDataTypes: Set<HKObjectType> = [HKWorkoutType.workoutType()] healthStore.requestAuthorization( toShare: nil, read: readDataTypes, completion: {(success, error) in if success { let predicate = HKQuery.predicateForWorkouts(with: HKWorkoutActivityType.swimming) let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false) let sampleQuery = HKSampleQuery( sampleType: HKObjectType.workoutType(), predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: [sortDescriptor], resultsHandler: {(_, results, error) in if error == nil { let workouts = results as! [HKWorkout] if let workout = workouts.first { print(workout.workoutEvents) } } }) healthStore.execute(sampleQuery) } })このコードをざっと説明すると、
1. ヘルスケアデータからワークアウトを取る許可を取得する
2. swimmingのワークアウトを最新から順に取得する
3. 最新のワークアウトのworkoutEventsを出力する
になります。
print文で出力したworkoutEventsから、サンプルとして一つを抜き出してみるとHKWorkoutEventTypeLap, <_NSConcreteDateInterval: 0x28300eb40> (Start Date) 2019-10-24 10:56:05 +0000 + (Duration) 6.520985 seconds = (End Date) 2019-10-24 10:56:11 +0000 { HKSwimmingStrokeStyle = 5;水泳のワークアウトの1ラップのデータと読み取れますが、ストローク数と思われるデータはありません。
公式のドキュメントを参照すると分かるのですが、HKWorkoutEventクラスにはストローク数と思われるものはありません。
また、HKWorkoutクラスにtotalSwimmingStrokeCountというものはあるのですが、これはワークアウト単位のものであって、各ラップのストローク数ではありません。
別のアプローチが必要です。そこで、ヘルスケアデータには水泳のストローク数というタイプがあるので、そこから取得しました。
水泳のストローク数から取得する
let readDataTypes: Set<HKObjectType> = [HKWorkoutType.workoutType(), HKObjectType.quantityType(forIdentifier: .swimmingStrokeCount)!]に書き換えて、先ほど取得したworkoutEventsを使って
let type = HKObjectType.quantityType(forIdentifier: .swimmingStrokeCount)! for (index, workoutEvent) in workoutEvents.enumerated() { // workoutEventのtypeにはlap, segment, pauseなどがあります if workoutEvent.type == .lap { let predicate = HKQuery.predicateForSamples(withStart: workoutEvent.dateInterval.start, end: workoutEvent.dateInterval.end, options: .strictStartDate) let query = HKStatisticsQuery(quantityType: type, quantitySamplePredicate: predicate, options: [.cumulativeSum]) { (_, statistic, error) in guard let statistic = statistic, error == nil else { return } let sum = statistic.sumQuantity()?.doubleValue(for: HKUnit.count()) ?? 0 print("stroke:\(sum) index:\(index)") } healthStore.execute(query) } }print文の箇所は非同期に実行されるため、インデックスも追加して、workoutEventsの何番目か分かるようにしました。
stroke:8.0 index:1 stroke:3.0 index:2 stroke:15.0 index:3 stroke:11.0 index:4 stroke:11.0 index:5 stroke:6.0 index:6 stroke:11.0 index:7 stroke:10.0 index:9 stroke:7.0 index:10 stroke:13.0 index:12 stroke:7.0 index:15 stroke:10.0 index:13 stroke:4.0 index:16 stroke:11.0 index:14出力はworkoutEventsの順番通りではありませんが、これでストローク数を取得できました。
HKStatisticsQueryの公式ドキュメント以外に参考になる事例を見つけられなかったので、
より良いやり方や、同じようなことを試みたことがあれば、コメント下さい。サンプルコードはこちらにも上げました。
ありがとうございました。
- 投稿日:2019-12-04T16:16:39+09:00
スクロールビュー
func scroll(){ //スクロール処理 scrollView = UIScrollView(frame: CGRect(origin: .zero, size: view.bounds.size)) imageView = UIImageView(image: UIImage(named: name)) scrollView.contentSize = imageView.bounds.size scrollView.addSubview(imageView) view.addSubview(scrollView) } func zoom(){ //ズーム機能 scrollView.minimumZoomScale = 0.005 scrollView.maximumZoomScale = 50 scrollView.delegate = self } func viewForZooming(in scrollView: UIScrollView) -> UIView? { return imageView }
- 投稿日:2019-12-04T15:29:25+09:00
Swift でテスト駆動開発をやってみた
弊社弊チームでは TDD を習得するために Kent Beck(著), 和田 卓人(翻訳)の テスト駆動開発 の輪読をしています。テスト駆動開発 はとても勉強になりました。
「写経ではなく別の言語で挑戦することで、TDDをより深く習得したい」 & 「Swift自体の勉強がしたい」と思い、Swift でテスト駆動開発(TDD)に挑戦してみました。
あと会社の先輩が Elm でTDD をしていたので真似してみました。
プロジェクトファイルはこちらに置いてあります。O-Junpei/TDD-Swift方針と注意事項
本文の内容やコードは、著作権を考慮して極力載せず、Swift のコードとテストのTODOリストだけでやっていきたいと思っています。Java のコードと似せるため型は明示的に書きました。
もし関係者の方々から注意喚起があれば記事をすぐに削除します。言語が異なるので進め方が多少異なる場合があります。事前準備
Include Unit Test にチェックを入れ、テスト環境が整ったプロジェクトを作成します。
第1章 仮実装
米ドルやフランを扱うことのできる多国通貨オプジェクトを作成します。
第1章では米ドルの掛け算を実装していきます。
あまり詰まることがなく、Java の実装を参考に Swift を書きことができました。TODOリスト
- $5 + 10CHF = $10 (レートが2:1の場合)
- $5 * 2 = $10
Dollar.swiftimport Foundation class Dollar { var amount: Int init(amount: Int) { self.amount = amount } func times(multiplier: Int) { amount *= multiplier } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Dollar = Dollar(amount: 5) five.times(multiplier: 2) XCTAssertEqual(10, five.amount) } }第2章 明白な実装
今章は詰まることがなくSwiftで実装することができました。
TODOリスト
- $5 + 10CHF = $10 (レートが2:1の場合)
$5 * 2= $10- amountをprivateにする
Dollarの副作用どうする?- Moneyの丸め処理どうする?
Dollar.swiftimport Foundation class Dollar { var amount: Int init(amount: Int) { self.amount = amount } func times(multiplier: Int) -> Dollar { return Dollar(amount: amount * multiplier) } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Dollar = Dollar(amount: 5) var product: Dollar = five.times(multiplier: 2) XCTAssertEqual(10, product.amount) product = five.times(multiplier: 3) XCTAssertEqual(15, product.amount) } }第3章 三角測量
Java の実装では
equals
メソッドで$ドル同士を比較しています。(Java ではプリミティブ型の比較に==
演算子、参照型の比較にequals
メソッドを使用する。)
Swift の実装では==
演算子を使用して比較しました。
Equatable
プロトコルに準拠することで==
演算子を使用することができます。TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
$5 * 2 = $10- amountをprivateにする
Dollarの副作用どうする?- Moneyの丸め処理どうする?
- equals()
Dollar.swiftimport Foundation class Dollar: Equatable { var amount: Int init(amount: Int) { self.amount = amount } func times(multiplier: Int) -> Dollar { return Dollar(amount: amount * multiplier) } static func == (lhs: Dollar, rhs: Dollar) -> Bool { lhs.amount == rhs.amount } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Dollar = Dollar(amount: 5) var product: Dollar = five.times(multiplier: 2) XCTAssertEqual(10, product.amount) product = five.times(multiplier: 3) XCTAssertEqual(15, product.amount) } func testEquality() { XCTAssertTrue(Dollar(amount: 5) == Dollar(amount: 5)) XCTAssertFalse(Dollar(amount: 5) == Dollar(amount: 6)) } }第4章 意図を語るテスト
今章は詰まることがなくSwiftで実装することができました。
TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
$5 * 2 = $10- amountをprivateにする
Dollarの副作用どうする?- Moneyの丸め処理どうする?
equals()- hashCode()
- nullとの等価性比較
- 他のオブジェクトとの等価性比較
Dollar.swiftimport Foundation class Dollar: Equatable { var amount: Int init(amount: Int) { self.amount = amount } func times(multiplier: Int) -> Dollar { return Dollar(amount: amount * multiplier) } static func == (lhs: Dollar, rhs: Dollar) -> Bool { lhs.amount == rhs.amount } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Dollar = Dollar(amount: 5) XCTAssertEqual(Dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Dollar(amount: 15), five.times(multiplier: 3)) } func testEquality() { XCTAssertTrue(Dollar(amount: 5) == Dollar(amount: 5)) XCTAssertFalse(Dollar(amount: 5) == Dollar(amount: 6)) } }第5章 原則をあえて破るとき
今章は詰まることがなくSwiftで実装することができました。
TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
$5 * 2 = $10amountをprivateにするDollarの副作用どうする?- Moneyの丸め処理どうする?
equals()- hashCode()
- nullとの等価性比較
- 他のオブジェクトとの等価性比較
- 5 CHF * 2 = 10 CHF
Dollar.swiftimport Foundation class Dollar: Equatable { var amount: Int init(amount: Int) { self.amount = amount } func times(multiplier: Int) -> Dollar { return Dollar(amount: amount * multiplier) } static func == (lhs: Dollar, rhs: Dollar) -> Bool { lhs.amount == rhs.amount } }Franc.swiftimport Foundation class Franc: Equatable { var amount: Int init(amount: Int) { self.amount = amount } func times(multiplier: Int) -> Franc { return Franc(amount: amount * multiplier) } static func == (lhs: Franc, rhs: Franc) -> Bool { lhs.amount == rhs.amount } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Dollar = Dollar(amount: 5) XCTAssertEqual(Dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Dollar(amount: 15), five.times(multiplier: 3)) } func testFrancMultiplication() { let five: Franc = Franc(amount: 5) XCTAssertEqual(Franc(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Franc(amount: 15), five.times(multiplier: 3)) } func testEquality() { XCTAssertTrue(Dollar(amount: 5) == Dollar(amount: 5)) XCTAssertFalse(Dollar(amount: 5) == Dollar(amount: 6)) } }第6章 テスト不足に気づいたら
今章は詰まることがなくSwiftで実装することができました。
TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
$5 * 2 = $10amountをprivateにするDollarの副作用どうする?- Moneyの丸め処理どうする?
equals()- hashCode()
- nullとの等価性比較
- 他のオブジェクトとの等価性比較
5 CHF * 2 = 10 CHF- DollarとFrancの重複
- equalsの一般化
- timesの一般化
Money.swiftclass Money: Equatable { let amount: Int init(amount: Int) { self.amount = amount } static func == (lhs: Money, rhs: Money) -> Bool { lhs.amount == rhs.amount } }Dollar.swiftclass Dollar: Money { override init(amount: Int) { super.init(amount: amount) } func times(multiplier: Int) -> Dollar { return Dollar(amount: amount * multiplier) } }Franc.swiftclass Franc: Money { override init(amount: Int) { super.init(amount: amount) } func times(multiplier: Int) -> Franc { return Franc(amount: amount * multiplier) } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Dollar = Dollar(amount: 5) XCTAssertEqual(Dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Dollar(amount: 15), five.times(multiplier: 3)) } func testFrancMultiplication() { let five: Franc = Franc(amount: 5) XCTAssertEqual(Franc(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Franc(amount: 15), five.times(multiplier: 3)) } func testEquality() { XCTAssertTrue(Dollar(amount: 5) == Dollar(amount: 5)) XCTAssertFalse(Dollar(amount: 5) == Dollar(amount: 6)) XCTAssertTrue(Franc(amount: 5) == Franc(amount: 5)) XCTAssertFalse(Franc(amount: 5) == Franc(amount: 6)) } }第7章 疑念をテストに翻訳する
7章ではフランが登場しました。
Java の実装ではinstanceOf
メソッドで型の比較をしています。
Swift の実装ではtype(of: XXX)
メソッドを使用することで比較しました。TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
$5 * 2 = $10amountをprivateにするDollarの副作用どうする?- Moneyの丸め処理どうする?
equals()- hashCode()
- nullとの等価性比較
- 他のオブジェクトとの等価性比較
5 CHF * 2 = 10 CHF- DollarとFrancの重複
equalsの一般化- timesの一般化
- FrancとDollarを比較する
Money.swiftclass Money: Equatable { let amount: Int init(amount: Int) { self.amount = amount } static func == (lhs: Money, rhs: Money) -> Bool { return lhs.amount == rhs.amount && String(describing: type(of: lhs)) == String(describing: type(of: rhs)) } }Dollar.swiftclass Dollar: Money { override init(amount: Int) { super.init(amount: amount) } func times(multiplier: Int) -> Dollar { return Dollar(amount: amount * multiplier) } }Franc.swiftclass Franc: Money { override init(amount: Int) { super.init(amount: amount) } func times(multiplier: Int) -> Franc { return Franc(amount: amount * multiplier) } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Dollar = Dollar(amount: 5) XCTAssertEqual(Dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Dollar(amount: 15), five.times(multiplier: 3)) } func testFrancMultiplication() { let five: Franc = Franc(amount: 5) XCTAssertEqual(Franc(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Franc(amount: 15), five.times(multiplier: 3)) } func testEquality() { XCTAssertTrue(Dollar(amount: 5) == Dollar(amount: 5)) XCTAssertFalse(Dollar(amount: 5) == Dollar(amount: 6)) XCTAssertTrue(Franc(amount: 5) == Franc(amount: 5)) XCTAssertFalse(Franc(amount: 5) == Franc(amount: 6)) XCTAssertFalse(Franc(amount: 5) == Dollar(amount: 5)) } }第8章 実装を隠す
今章は少し困りました。
Java の実装で Abstract クラスが登場したためです。
Swift には Abstract クラスに相当するものが無いため、Money
クラスを具象クラスとして定義してしまいました。
もしもっと良い書き方があればコメントいただきたいです。。。!TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
$5 * 2 = $10amountをprivateにするDollarの副作用どうする?- Moneyの丸め処理どうする?
equals()- hashCode()
- nullとの等価性比較
- 他のオブジェクトとの等価性比較
5 CHF * 2 = 10 CHF- DollarとFrancの重複
equalsの一般化- timesの一般化
FrancとDollarを比較する- 通過の概念
Money.swiftclass Money: Equatable { let amount: Int init(amount: Int) { self.amount = amount } static func == (lhs: Money, rhs: Money) -> Bool { return lhs.amount == rhs.amount && String(describing: type(of: lhs)) == String(describing: type(of: rhs)) } static func dollar(amount: Int) -> Money { return Dollar(amount: amount) } static func franc(amount: Int) -> Money { return Franc(amount: amount) } func times(multiplier: Int) -> Money { fatalError() } }Dollar.swiftclass Dollar: Money { override init(amount: Int) { super.init(amount: amount) } override func times(multiplier: Int) -> Money { return Dollar(amount: amount * multiplier) } }Franc.swiftclass Franc: Money { override init(amount: Int) { super.init(amount: amount) } override func times(multiplier: Int) -> Money { return Franc(amount: amount * multiplier) } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Money = Money.dollar(amount: 5) XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3)) } func testFrancMultiplication() { let five: Money = Money.franc(amount: 5) XCTAssertEqual(Money.franc(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.franc(amount: 15), five.times(multiplier: 3)) } func testEquality() { XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5)) XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6)) XCTAssertTrue(Money.franc(amount: 5) == Money.franc(amount: 5)) XCTAssertFalse(Money.franc(amount: 5) == Money.franc(amount: 6)) XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5)) } }第9章 歩幅の調整
今章は詰まることがなくSwiftで実装することができました。
TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
$5 * 2 = $10amountをprivateにするDollarの副作用どうする?- Moneyの丸め処理どうする?
equals()- hashCode()
- nullとの等価性比較
- 他のオブジェクトとの等価性比較
5 CHF * 2 = 10 CHF- DollarとFrancの重複
equalsの一般化- timesの一般化
FrancとDollarを比較する- 通過の概念
- testFrancMultiplicationを削除する
Money.swiftclass Money: Equatable { let amount: Int let currency: String init(amount: Int, currency: String) { self.amount = amount self.currency = currency } static func == (lhs: Money, rhs: Money) -> Bool { return lhs.amount == rhs.amount && String(describing: type(of: lhs)) == String(describing: type(of: rhs)) } static func dollar(amount: Int) -> Money { return Dollar(amount: amount, currency: "USD") } static func franc(amount: Int) -> Money { return Franc(amount: amount, currency: "CHF") } func times(multiplier: Int) -> Money { fatalError("Must be overridden") } }Dollar.swiftclass Dollar: Money { override init(amount: Int, currency: String) { super.init(amount: amount, currency: currency) } override func times(multiplier: Int) -> Money { return Money.dollar(amount: amount * multiplier) } }Franc.swiftclass Franc: Money { override init(amount: Int, currency: String) { super.init(amount: amount, currency: currency) } override func times(multiplier: Int) -> Money { return Money.franc(amount: amount * multiplier) } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Money = Money.dollar(amount: 5) XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3)) } func testFrancMultiplication() { let five: Money = Money.franc(amount: 5) XCTAssertEqual(Money.franc(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.franc(amount: 15), five.times(multiplier: 3)) } func testEquality() { XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5)) XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6)) XCTAssertTrue(Money.franc(amount: 5) == Money.franc(amount: 5)) XCTAssertFalse(Money.franc(amount: 5) == Money.franc(amount: 6)) XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5)) } func testCurrency() { XCTAssertEqual("USD", Money.dollar(amount: 1).currency) XCTAssertEqual("CHF", Money.franc(amount: 1).currency) } }第10章 テストに聞いてみる
エラーメッセージをわかりやすくするため、Java の実装では
toString
メソッドを作成しました。
Swift でtoString
メソッドに相当するものはdescription
Computed property であるため、CustomStringConvertible
プロトコルに準拠して実装しました。TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
$5 * 2 = $10amountをprivateにするDollarの副作用どうする?- Moneyの丸め処理どうする?
equals()- hashCode()
- nullとの等価性比較
- 他のオブジェクトとの等価性比較
5 CHF * 2 = 10 CHF- DollarとFrancの重複
equalsの一般化- timesの一般化
FrancとDollarを比較する通過の概念- testFrancMultiplicationを削除する
Money.swiftclass Money: Equatable, CustomStringConvertible { let amount: Int let currency: String init(amount: Int, currency: String) { self.amount = amount self.currency = currency } var description: String { return "\(amount) \(currency.description))" } static func == (lhs: Money, rhs: Money) -> Bool { return lhs.amount == rhs.amount && lhs.currency == rhs.currency } static func dollar(amount: Int) -> Money { return Dollar(amount: amount, currency: "USD") } static func franc(amount: Int) -> Money { return Franc(amount: amount, currency: "CHF") } func times(multiplier: Int) -> Money { return Money(amount: amount * multiplier, currency: currency) } }Dollar.swiftclass Dollar: Money { override init(amount: Int, currency: String) { super.init(amount: amount, currency: currency) } }Franc.swiftclass Franc: Money { override init(amount: Int, currency: String) { super.init(amount: amount, currency: currency) } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Money = Money.dollar(amount: 5) XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3)) } func testFrancMultiplication() { let five: Money = Money.franc(amount: 5) XCTAssertEqual(Money.franc(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.franc(amount: 15), five.times(multiplier: 3)) } func testEquality() { XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5)) XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6)) XCTAssertTrue(Money.franc(amount: 5) == Money.franc(amount: 5)) XCTAssertFalse(Money.franc(amount: 5) == Money.franc(amount: 6)) XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5)) } func testCurrency() { XCTAssertEqual("USD", Money.dollar(amount: 1).currency) XCTAssertEqual("CHF", Money.franc(amount: 1).currency) } func testDifferentClassEquality() { XCTAssertTrue(Money(amount: 10, currency: "CHF") == Franc(amount: 10, currency: "CHF")) } }第11章 不要になったら消す
今章は詰まることがなくSwiftで実装することができました。
TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
$5 * 2 = $10amountをprivateにするDollarの副作用どうする?- Moneyの丸め処理どうする?
equals()- hashCode()
- nullとの等価性比較
- 他のオブジェクトとの等価性比較
5 CHF * 2 = 10 CHF- DollarとFrancの重複
equalsの一般化timesの一般化FrancとDollarを比較する通過の概念- testFrancMultiplicationを削除する
Money.swiftclass Money: Equatable, CustomStringConvertible { let amount: Int let currency: String init(amount: Int, currency: String) { self.amount = amount self.currency = currency } var description: String { return "\(amount) \(currency.description))" } static func == (lhs: Money, rhs: Money) -> Bool { return lhs.amount == rhs.amount && lhs.currency == rhs.currency } static func dollar(amount: Int) -> Money { return Money(amount: amount, currency: "USD") } static func franc(amount: Int) -> Money { return Money(amount: amount, currency: "CHF") } func times(multiplier: Int) -> Money { return Money(amount: amount * multiplier, currency: currency) } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Money = Money.dollar(amount: 5) XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3)) } func testEquality() { XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5)) XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6)) XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5)) } func testCurrency() { XCTAssertEqual("USD", Money.dollar(amount: 1).currency) XCTAssertEqual("CHF", Money.franc(amount: 1).currency) } }第12章 設計とメタファー
今章は詰まることがなくSwiftで実装することができました。
TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
- $5 + $5 = $10
Money.swiftclass Money: Equatable, CustomStringConvertible, Expression { let amount: Int let currency: String init(amount: Int, currency: String) { self.amount = amount self.currency = currency } var description: String { return "\(amount) \(currency.description))" } static func == (lhs: Money, rhs: Money) -> Bool { return lhs.amount == rhs.amount && lhs.currency == rhs.currency } static func dollar(amount: Int) -> Money { return Money(amount: amount, currency: "USD") } static func franc(amount: Int) -> Money { return Money(amount: amount, currency: "CHF") } func times(multiplier: Int) -> Money { return Money(amount: amount * multiplier, currency: currency) } func plus(addend: Money) -> Expression { return Money(amount: amount + addend.amount, currency: currency) } }Expression.swiftprotocol Expression { }Bank.swiftclass Bank { func reduce(source: Expression, to: String) -> Money { return Money.dollar(amount: 10) } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Money = Money.dollar(amount: 5) XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3)) } func testEquality() { XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5)) XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6)) XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5)) } func testCurrency() { XCTAssertEqual("USD", Money.dollar(amount: 1).currency) XCTAssertEqual("CHF", Money.franc(amount: 1).currency) } }第13章 実装を導くテスト
Java の実装の途中で型のキャストを行う箇所があります。
if let
構文で安全にキャストすることができます。if let money = source as? Money { return money }TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
- $5 + $5 = $10
Money.swiftclass Money: Equatable, CustomStringConvertible, Expression { let amount: Int let currency: String init(amount: Int, currency: String) { self.amount = amount self.currency = currency } var description: String { return "\(amount) \(currency.description))" } static func == (lhs: Money, rhs: Money) -> Bool { return lhs.amount == rhs.amount && lhs.currency == rhs.currency } static func dollar(amount: Int) -> Money { return Money(amount: amount, currency: "USD") } static func franc(amount: Int) -> Money { return Money(amount: amount, currency: "CHF") } func times(multiplier: Int) -> Money { return Money(amount: amount * multiplier, currency: currency) } func plus(addend: Money) -> Expression { return Sum(augend: self, addend: addend) } func reduce(to: String)-> Money { return self } }Sum.swiftclass Sum: Expression { let augend: Money let addend: Money init(augend: Money, addend: Money) { self.augend = augend self.addend = addend } func reduce(to: String) -> Money { let amount: Int = augend.amount + addend.amount return Money(amount: amount, currency: to) } }Expression.swiftprotocol Expression { func reduce(to: String) -> Money }Bank.swiftclass Bank { func reduce(source: Expression, to: String) -> Money { return source.reduce(to: to) } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Money = Money.dollar(amount: 5) XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3)) } func testEquality() { XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5)) XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6)) XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5)) } func testCurrency() { XCTAssertEqual("USD", Money.dollar(amount: 1).currency) XCTAssertEqual("CHF", Money.franc(amount: 1).currency) } func testSimpleAddition() { let five: Money = Money.dollar(amount: 5) let sum: Expression = five.plus(addend: five) let bank: Bank = Bank() let reduced: Money = bank.reduce(source: sum, to: "USD") XCTAssertEqual(Money.dollar(amount: 10), reduced) } func testPlusReturnSum() { let five: Money = Money.dollar(amount: 5) let result: Expression = five.plus(addend: five) let sum: Sum = result as! Sum XCTAssertEqual(five, sum.addend) } func testResuceSum() { let sum: Expression = Sum(augend: Money.dollar(amount: 3), addend: Money.dollar(amount: 4)) let bank: Bank = Bank() let result: Money = bank.reduce(source: sum, to: "USD") XCTAssertEqual(Money.dollar(amount: 7), result) } func testReduceMoney() { let bank: Bank = Bank() let result: Money = bank.reduce(source: Money.dollar(amount: 1), to: "USD") XCTAssertEqual(Money.dollar(amount: 1), result) } }第14章 学習用テストと回帰テスト
今章は詰まることがなくSwiftで実装することができました。
TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
- $5 + $5 = $10
- $5 + $5 が Money を返す
Bank.reduce(Money)- Money を変換して換算を行う
- Reduce(Bank, String)
Money.swiftclass Money: Equatable, CustomStringConvertible, Expression { let amount: Int let currency: String init(amount: Int, currency: String) { self.amount = amount self.currency = currency } var description: String { return "\(amount) \(currency.description))" } static func == (lhs: Money, rhs: Money) -> Bool { return lhs.amount == rhs.amount && lhs.currency == rhs.currency } static func dollar(amount: Int) -> Money { return Money(amount: amount, currency: "USD") } static func franc(amount: Int) -> Money { return Money(amount: amount, currency: "CHF") } func times(multiplier: Int) -> Money { return Money(amount: amount * multiplier, currency: currency) } func plus(addend: Money) -> Expression { return Sum(augend: self, addend: addend) } func reduce(bank: Bank, to: String)-> Money { let rate: Int = bank.rate(from: currency, to: to) return Money(amount: amount / rate, currency: to) } }Sum.swiftclass Sum: Expression { let augend: Money let addend: Money init(augend: Money, addend: Money) { self.augend = augend self.addend = addend } func reduce(bank:Bank, to: String) -> Money { let amount: Int = augend.amount + addend.amount return Money(amount: amount, currency: to) } }Expression.swiftprotocol Expression { func reduce(bank: Bank, to: String) -> Money }Bank.swiftclass Bank { private var rates: [Pair: Int] = [:] func reduce(source: Expression, to: String) -> Money { return source.reduce(bank: self, to: to) } func addRate(from: String, to: String, rate: Int) { rates[Pair(from: from, to: to)] = rate } func rate(from: String, to: String) -> Int { if from == to { return 1 } guard let rate = rates[Pair(from: from, to: to)] else { fatalError("未対応の通過です") } return rate } }Pair.swiftclass Pair: Hashable { private let from: String private let to: String init(from: String, to: String) { self.from = from self.to = to } func hash(into hasher: inout Hasher) { hasher.combine(from) hasher.combine(to) } static func == (lhs: Pair, rhs: Pair) -> Bool { return lhs.from == rhs.from && lhs.to == rhs.to } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Money = Money.dollar(amount: 5) XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3)) } func testEquality() { XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5)) XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6)) XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5)) } func testCurrency() { XCTAssertEqual("USD", Money.dollar(amount: 1).currency) XCTAssertEqual("CHF", Money.franc(amount: 1).currency) } func testSimpleAddition() { let five: Money = Money.dollar(amount: 5) let sum: Expression = five.plus(addend: five) let bank: Bank = Bank() let reduced: Money = bank.reduce(source: sum, to: "USD") XCTAssertEqual(Money.dollar(amount: 10), reduced) } func testPlusReturnSum() { let five: Money = Money.dollar(amount: 5) let result: Expression = five.plus(addend: five) let sum: Sum = result as! Sum XCTAssertEqual(five, sum.addend) } func testResuceSum() { let sum: Expression = Sum(augend: Money.dollar(amount: 3), addend: Money.dollar(amount: 4)) let bank: Bank = Bank() let result: Money = bank.reduce(source: sum, to: "USD") XCTAssertEqual(Money.dollar(amount: 7), result) } func testReduceMoney() { let bank: Bank = Bank() let result: Money = bank.reduce(source: Money.dollar(amount: 1), to: "USD") XCTAssertEqual(Money.dollar(amount: 1), result) } func testReduceMoneyDifferentCurrency() { let bank: Bank = Bank() bank.addRate(from: "CHF", to: "USD", rate: 2) let result: Money = bank.reduce(source: Money.franc(amount: 2), to: "USD") XCTAssertEqual(Money.dollar(amount: 1), result) } func testIdentityRate() { XCTAssertEqual(1, Bank().rate(from: "USD", to: "USD")) } }第15章 テスト任せとコンパイラ任せ
Java の実装を参考にしながら Swift で実装したところ、
Protocol type 'Expression' cannot conform to 'Equatable' because only concrete types can conform to protocols
と怒られてしまいました。
Swift だとprotcol
同士の比較ができなそうなので、Money
クラスで比較しました。TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
$5 + $5 = $10- $5 + $5 = $10がMoneyを返す
Bank.reduce(Money)Moneyを変換して換算を行うReduce(Bank, String)Money.swiftclass Money: Equatable, CustomStringConvertible, Expression { let amount: Int let currency: String init(amount: Int, currency: String) { self.amount = amount self.currency = currency } var description: String { return "\(amount) \(currency.description))" } static func == (lhs: Money, rhs: Money) -> Bool { return lhs.amount == rhs.amount && lhs.currency == rhs.currency } static func dollar(amount: Int) -> Money { return Money(amount: amount, currency: "USD") } static func franc(amount: Int) -> Money { return Money(amount: amount, currency: "CHF") } func times(multiplier: Int) -> Expression { return Money(amount: amount * multiplier, currency: currency) } func plus(addend: Expression) -> Expression { return Sum(augend: self, addend: addend) } func reduce(bank: Bank, to: String)-> Money { let rate: Int = bank.rate(from: currency, to: to) return Money(amount: amount / rate, currency: to) } }Sum.swiftclass Sum: Expression { let augend: Expression let addend: Expression init(augend: Expression, addend: Expression) { self.augend = augend self.addend = addend } func plus(addend: Expression) -> Expression { fatalError() } func reduce(bank: Bank, to: String) -> Money { let amount: Int = augend.reduce(bank: bank, to: to).amount + addend.reduce(bank: bank, to: to).amount return Money(amount: amount, currency: to) } }Expression.swiftprotocol Expression { func plus(addend: Expression) -> Expression func reduce(bank: Bank, to: String) -> Money }Bank.swiftclass Bank { private var rates: [Pair: Int] = [:] func reduce(source: Expression, to: String) -> Money { return source.reduce(bank: self, to: to) } func addRate(from: String, to: String, rate: Int) { rates[Pair(from: from, to: to)] = rate } func rate(from: String, to: String) -> Int { if from == to { return 1 } guard let rate = rates[Pair(from: from, to: to)] else { fatalError("未対応の通過です") } return rate } }Pair.swiftclass Pair: Hashable { private let from: String private let to: String init(from: String, to: String) { self.from = from self.to = to } func hash(into hasher: inout Hasher) { hasher.combine(from) hasher.combine(to) } static func == (lhs: Pair, rhs: Pair) -> Bool { return lhs.from == rhs.from && lhs.to == rhs.to } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Money = Money.dollar(amount: 5) // XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2).reduce(bank: Bank(), to: "USD")) // XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3)) XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3).reduce(bank: Bank(), to: "USD")) } func testEquality() { XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5)) XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6)) XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5)) } func testCurrency() { XCTAssertEqual("USD", Money.dollar(amount: 1).currency) XCTAssertEqual("CHF", Money.franc(amount: 1).currency) } func testSimpleAddition() { let five: Money = Money.dollar(amount: 5) let sum: Expression = five.plus(addend: five) let bank: Bank = Bank() let reduced: Money = bank.reduce(source: sum, to: "USD") XCTAssertEqual(Money.dollar(amount: 10), reduced) } func testPlusReturnSum() { let five: Money = Money.dollar(amount: 5) let result: Expression = five.plus(addend: five) let sum: Sum = result as! Sum // XCTAssertEqual(five, sum.added) XCTAssertEqual(five, sum.addend.reduce(bank: Bank(), to: "USD")) } func testResuceSum() { let sum: Expression = Sum(augend: Money.dollar(amount: 3), addend: Money.dollar(amount: 4)) let bank: Bank = Bank() let result: Money = bank.reduce(source: sum, to: "USD") XCTAssertEqual(Money.dollar(amount: 7), result) } func testReduceMoney() { let bank: Bank = Bank() let result: Money = bank.reduce(source: Money.dollar(amount: 1), to: "USD") XCTAssertEqual(Money.dollar(amount: 1), result) } func testReduceMoneyDifferentCurrency() { let bank: Bank = Bank() bank.addRate(from: "CHF", to: "USD", rate: 2) let result: Money = bank.reduce(source: Money.franc(amount: 2), to: "USD") XCTAssertEqual(Money.dollar(amount: 1), result) } func testIdentityRate() { XCTAssertEqual(1, Bank().rate(from: "USD", to: "USD")) } func testMixedAddition() { let fiveBucks: Expression = Money.dollar(amount: 5) let tenFrancs: Expression = Money.franc(amount: 10) let bank: Bank = Bank() bank.addRate(from: "CHF", to: "USD", rate: 2) let result: Money = bank.reduce(source: fiveBucks.plus(addend: tenFrancs), to: "USD") XCTAssertEqual(Money.dollar(amount: 10), result) } }第16章 将来の読み手を考えたテスト
今章は詰まることがなくSwiftで実装することができました。
TODOリスト
- $5 + 10CHF = $10(レートが2:1の場合)
$5 + $5 = $10- $5 + $5 = $10がMoneyを返す
Bank.reduce(Money)Moneyを変換して換算を行うReduce(Bank, String)- Sum.plus
- Expression.times
Money.swiftclass Money: Equatable, CustomStringConvertible, Expression { let amount: Int let currency: String init(amount: Int, currency: String) { self.amount = amount self.currency = currency } var description: String { return "\(amount) \(currency.description))" } static func == (lhs: Money, rhs: Money) -> Bool { return lhs.amount == rhs.amount && lhs.currency == rhs.currency } static func dollar(amount: Int) -> Money { return Money(amount: amount, currency: "USD") } static func franc(amount: Int) -> Money { return Money(amount: amount, currency: "CHF") } func times(multiplier: Int) -> Expression { return Money(amount: amount * multiplier, currency: currency) } func plus(addend: Expression) -> Expression { return Sum(augend: self, addend: addend) } func reduce(bank: Bank, to: String)-> Money { let rate: Int = bank.rate(from: currency, to: to) return Money(amount: amount / rate, currency: to) } }Sum.swiftclass Sum: Expression { let augend: Expression let addend: Expression init(augend: Expression, addend: Expression) { self.augend = augend self.addend = addend } func times(multiplier: Int) -> Expression { return Sum(augend: augend.times(multiplier: multiplier), addend: addend.times(multiplier: multiplier)) } func plus(addend: Expression) -> Expression { return Sum(augend: self, addend: addend) } func reduce(bank: Bank, to: String) -> Money { let amount: Int = augend.reduce(bank: bank, to: to).amount + addend.reduce(bank: bank, to: to).amount return Money(amount: amount, currency: to) } }Expression.swiftprotocol Expression { func times(multiplier: Int) -> Expression func plus(addend: Expression) -> Expression func reduce(bank: Bank, to: String) -> Money }Bank.swiftclass Bank { private var rates: [Pair: Int] = [:] func reduce(source: Expression, to: String) -> Money { return source.reduce(bank: self, to: to) } func addRate(from: String, to: String, rate: Int) { rates[Pair(from: from, to: to)] = rate } func rate(from: String, to: String) -> Int { if from == to { return 1 } guard let rate = rates[Pair(from: from, to: to)] else { fatalError("未対応の通過です") } return rate } }Pair.swiftclass Pair: Hashable { private let from: String private let to: String init(from: String, to: String) { self.from = from self.to = to } func hash(into hasher: inout Hasher) { hasher.combine(from) hasher.combine(to) } static func == (lhs: Pair, rhs: Pair) -> Bool { return lhs.from == rhs.from && lhs.to == rhs.to } }TDD_SwiftTests.swiftimport XCTest @testable import TDD_Swift class TDD_SwiftTests: XCTestCase { func testMultiplication() { let five: Money = Money.dollar(amount: 5) // XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2)) XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2).reduce(bank: Bank(), to: "USD")) // XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3)) XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3).reduce(bank: Bank(), to: "USD")) } func testEquality() { XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5)) XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6)) XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5)) } func testCurrency() { XCTAssertEqual("USD", Money.dollar(amount: 1).currency) XCTAssertEqual("CHF", Money.franc(amount: 1).currency) } func testSimpleAddition() { let five: Money = Money.dollar(amount: 5) let sum: Expression = five.plus(addend: five) let bank: Bank = Bank() let reduced: Money = bank.reduce(source: sum, to: "USD") XCTAssertEqual(Money.dollar(amount: 10), reduced) } func testPlusReturnSum() { let five: Money = Money.dollar(amount: 5) let result: Expression = five.plus(addend: five) let sum: Sum = result as! Sum // XCTAssertEqual(five, sum.added) XCTAssertEqual(five, sum.addend.reduce(bank: Bank(), to: "USD")) } func testResuceSum() { let sum: Expression = Sum(augend: Money.dollar(amount: 3), addend: Money.dollar(amount: 4)) let bank: Bank = Bank() let result: Money = bank.reduce(source: sum, to: "USD") XCTAssertEqual(Money.dollar(amount: 7), result) } func testReduceMoney() { let bank: Bank = Bank() let result: Money = bank.reduce(source: Money.dollar(amount: 1), to: "USD") XCTAssertEqual(Money.dollar(amount: 1), result) } func testReduceMoneyDifferentCurrency() { let bank: Bank = Bank() bank.addRate(from: "CHF", to: "USD", rate: 2) let result: Money = bank.reduce(source: Money.franc(amount: 2), to: "USD") XCTAssertEqual(Money.dollar(amount: 1), result) } func testIdentityRate() { XCTAssertEqual(1, Bank().rate(from: "USD", to: "USD")) } func testMixedAddition() { let fiveBucks: Expression = Money.dollar(amount: 5) let tenFrancs: Expression = Money.franc(amount: 10) let bank: Bank = Bank() bank.addRate(from: "CHF", to: "USD", rate: 2) let result: Money = bank.reduce(source: fiveBucks.plus(addend: tenFrancs), to: "USD") XCTAssertEqual(Money.dollar(amount: 10), result) } func testSumPlusMoney() { let fiveBucks: Expression = Money.dollar(amount: 5) let tenFrancs: Expression = Money.franc(amount: 10) let bank: Bank = Bank() bank.addRate(from: "CHF", to: "USD", rate: 2) let sum: Expression = Sum(augend: fiveBucks, addend: tenFrancs).plus(addend: fiveBucks) let result = bank.reduce(source: sum, to: "USD") XCTAssertEqual(Money.dollar(amount: 15), result) } func testSumTimes() { let fiveBucks: Expression = Money.dollar(amount: 5) let tenFrancs: Expression = Money.franc(amount: 10) let bank: Bank = Bank() bank.addRate(from: "CHF", to: "USD", rate: 2) let sum: Expression = Sum(augend: fiveBucks, addend: tenFrancs).times(multiplier: 2) let result: Money = bank.reduce(source: sum, to: "USD") XCTAssertEqual(Money.dollar(amount: 20), result) } }まとめ
Swift で TDD ができて楽しかったです。
Swift TDD をしようとしている人の参考になれば嬉しいです。参考
- 投稿日:2019-12-04T15:09:08+09:00
【swift】xcodeの基礎ショートカット/文法
- cmd + R : ビルドラン
- ctr + E : 行の最後に移動
- ctr + A : 行の最初に移動
- cmd + shift + F(O) : プロジェクト内検索
- cmd + クリック : そこへとぶ
- ctr + i : インデントの調整(範囲選択が必要)
- option + クリック : クラスを2画面ひらける
- cmd + option + / : メソッドのコメント
sample.swift// 変数 var // 定数 let // null nill // 数字を文字列に変換 string(a) // boolean var a : Bool = true // 関数(戻り値->int) func myFunc(value:Int)->Int{ } // 画像をセット let image = UImage(named:Const.LogoImage)
- 投稿日:2019-12-04T10:38:58+09:00
[はじめてのiOSアプリ]xcodeで地図アプリを作成(その6)
はじめに
iOSアプリを作ってみたいけど
何から始めて良いのかわからないとりあえず、
「やってみました」記事を参考に
地図アプリを真似てみようと思うという記事の6回目です。
今回は、拡大・縮小ボタンを追加します。
- ピンチイン・アウトあるから、拡大・縮小ボタンは不要では?
- Simulatorでピンチアウト(Ctrl+Option+Click)するのは直感的じゃなく忘れがちじゃない?
- 直感的なボタンがあれば、ピンチアウトの操作を忘れても影響ないから
- 「やってみました」記事だし、ボタンを配置してみたかったから
ボタンを追加
- ボタンを配置
- [Main storyboard]を表示する
- 【なぜ?】
- [MKMapView]の上にボタンを配置する前準備
- メニューから[View]-[Show Library]を選択、表示されるウインドウで [button] と入力し [Button]を絞り込む
![]()
- 【なぜ?】
- MapViewにボタンを配置したいから
- 絞り込まなくても、探し出すことができればOK
- [Button]をドラッグ&ドロップでボタンをMapViewに配置する(「拡大」「縮小」の2箇所)
- ボタンの文字[Button]を変更
- メニューから[View]-[Inspectors]-[Show Attribute Inspector]を選択
- [MapView]に表示されているボタンを選択し、[Attribute Inspector]に表示されている文字サイズを好きなサイズに変更
- サイズ変更に合わせ、ボタン位置も調整
ボタンの関連づけ
- (以前の記事にも書いたように)[ViewController.swift]と[Main storyboard]を同時に表示する
- 【なぜ?】
- [Main storyboard]と[ViewController.swift]が同時に表示されていると[ボタン]の関連づけが容易にできるため
- Ctrl+クリックで「+」ボタンのOutletを表示
[Touch Down]の右に表示されている○印を[ViewController.swift]にドラッグ&ドロップ
- ドロップ場所は、以下の場所
ViewController.swiftvar locationManager: CLLocationManager! // ここ付近にドラッグ&ドロップ override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view.接続情報の入力を促すアイアログが表示されるので[Name]に[clickZoomin]と入力
同様に「ー」ボタンにも同じような操作([Name]は[clickZoomout]とする)を実施
- 【なぜ?】
- 「ー」ボタンでも同じような操作を行うため
- ViewController.swiftの関連する部分は、以下のようになっているはず
ViewController.swiftclass ViewController: UIViewController, CLLocationManagerDelegate { @IBOutlet var mapView: MKMapView! var locationManager: CLLocationManager! @IBAction func clickZoomin(_ sender: Any) { } @IBAction func clickZoomout(_ sender: Any) { } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view.ボタンに対応する処理を記述
ViewController.swiftの該当部分を以下のように修正
- 【なぜ?】
- 今回は、ボタンをタップしたことが判別できればOKだから
ViewController.swift@IBAction func clickZoomin(_ sender: Any) { print("[DBG]clickZoomin") } @IBAction func clickZoomout(_ sender: Any) { print("[DBG]clickZoomout") }テスト実行
- Simulatorを起動し、「+」ボタンや「ー」ボタンを押してみる
- ボタンを押すたびに、Xcodeのログに位置情報に混ざり[clickZoomin][clickZoomout]出力された
log[DBG]latitude : 37.33124551 [DBG]clickZoomout [DBG]longitude : -122.03073097 [DBG]latitude : 37.33121136 [DBG]clickZoomin [DBG]clickZoomout [DBG]longitude : -122.03072292 [DBG]latitude : 37.33117775 [DBG]clickZoomin [DBG]longitude : -122.03071071 [DBG]latitude : 37.33114614 [DBG]clickZoomout [DBG]longitude : -122.03069859今回の到達点
- 画面に「拡大」「縮小」ボタンを配置することができた
- ボタンをタップすると、デバッグメッセージが表示された
連載
- 投稿日:2019-12-04T09:50:32+09:00
忘備録-Swiftのオプショナル型
趣味でIOSアプリ開発をかじっていた自分が、改めてSwiftを勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。
参考文献
この記事は以下の書籍の情報を参考にして執筆しました。
if-let文
if-letでunwrapするときに複数の変数を指定できる
var hoge: String? = "hoge" var fuga: String? = "fuga" if let h = hoge, let f = fuga { print("nilじゃないよ") }guard文
guardの後に宣言した条件を満たさない場合、else節の処理を実行
func hogehoge() { //let hoge: String? = "hoge" let hoge: String? = nil guard hoge == nil else{ //guardの条件じゃ無いなら下記の文が実行される。この場合nilじゃ無いなら実行 print(hoge!) return } print(hoge) //nil return } hogehoge()nil合体演算子
ある変数がnilだった場合に別の値をセットしたいとき
let hoge: String? = nil print(hoge ?? "hogehoge") //hogeの値がnilなら"hogehoge"を使う有値オプショナル型
let hoge: String! = "hoge" let fuga: String? = "fuga" let message1:String = hoge let message2:String = fuga // error unwrapしないといけない有値オプショナル型はコンパイラが値の評価の時に特別扱いするだけで、オプショナル型と変わらない。
let message3:String! = fuga let message4:String? = hoge失敗のあるイニシャライザ
初期化に失敗した場合return nilをして処理を修了できる。
struct Hoge { var hoge: String init?(hoge: String?) { guard let h = hoge else{ return nil } self.hoge = h } } let fuga = Hoge(hoge: nil) if let f = fuga{ print(fuga!.hoge) } else { print(fuga) // nil }
- 投稿日:2019-12-04T09:50:32+09:00
Swiftのオプショナル型
趣味でIOSアプリ開発をかじっていた自分が、改めてSwiftを勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。
参考文献
この記事は以下の書籍の情報を参考にして執筆しました。
if-let文
if-letでunwrapするときに複数の変数を指定できる
var hoge: String? = "hoge" var fuga: String? = "fuga" if let h = hoge, let f = fuga { print("nilじゃないよ") }guard文
guardの後に宣言した条件を満たさない場合、else節の処理を実行
func hogehoge() { //let hoge: String? = "hoge" let hoge: String? = nil guard hoge == nil else{ //guardの条件じゃ無いなら下記の文が実行される。この場合nilじゃ無いなら実行 print(hoge!) return } print(hoge) //nil return } hogehoge()nil合体演算子
ある変数がnilだった場合に別の値をセットしたいとき
let hoge: String? = nil print(hoge ?? "hogehoge") //hogeの値がnilなら"hogehoge"を使う有値オプショナル型
let hoge: String! = "hoge" let fuga: String? = "fuga" let message1:String = hoge let message2:String = fuga // error unwrapしないといけない有値オプショナル型はコンパイラが値の評価の時に特別扱いするだけで、オプショナル型と変わらない。
let message3:String! = fuga let message4:String? = hoge失敗のあるイニシャライザ
初期化に失敗した場合return nilをして処理を修了できる。
struct Hoge { var hoge: String init?(hoge: String?) { guard let h = hoge else{ return nil } self.hoge = h } } let fuga = Hoge(hoge: nil) if let f = fuga{ print(fuga!.hoge) } else { print(fuga) // nil }
- 投稿日:2019-12-04T09:42:24+09:00
忘備録-Swiftの構造体
趣味でIOSアプリ開発をかじっていた自分が、改めてSwiftを勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。
参考文献
この記事は以下の書籍の情報を参考にして執筆しました。
構造体の初期化
カスタムイニシャライザ : 構造体ごとに定義できる独自のイニシャライザ
カスタムイニシャライザが定義されてないとき、自動的にシンプルなイニシャライザが使える。既定イニシャライザ : 構造体の各プロパティに初期値が指定されている場合、型名(構造体の名前)()と書くことでインスタンスを生成できる。
struct Book { var name = "hoge book" var page = 125 } var fuga = Book() print(fuga.name, fuga.page) // hoge book 125全項目イニシャライザ : 各プロパディ名を引数として書き連ねる。
var dogBook = Book(name: "dog book") print(dogBook.name, dogBook.page) // dog book 125 var oneBook = Book(page: 1) print(oneBook.name, oneBook.page) // hoge book 1全項目イニシャライザを使用する場合はプロパティに初期値を設定する必要がない。
その代わりに初期値が設定されていないプロパティは必ず引数として値を渡す必要がある。struct Book { var name : String var page = 125 } var fuga = Book(name: "hoge book") // hoge book 125 print(fuga.name, fuga.page) var dogBook = Book(name: "dog book") // dog book 125 print(dogBook.name, dogBook.page) var oneBook = Book(page: 1) // error定数に構造体を代入した場合
定数に代入された構造体のインスタンスは、各プロパディの値を変更できない。
この制約は構造体が値型であるためであり、クラスのインスタンスとは違う。(クラスのインスタンスは後ほど勉強します。)struct Book { var name : String var page = 125 } var hoge = Book(name: "hoge book") hoge.name = "fuga book" print(hoge.name, hoge.page) // fuga book 125 let fuga = Book(name: "hoge book") fuga.name = "fuga book" // error構造体に定数プロパティを宣言する場合
最初の一回しか値をセットできない。
struct Book { let name : String = "book" var page = 125 } var hoge = Book(name: "hoge book") // errorstruct Book { let name : String var page = 125 } var hoge = Book(name: "hoge book") hoge.name = "fuga book" //errorカスタムイニシャライザ
initを使って宣言
struct Book { var name : String var page : Int init() { name = " hoge book" //全てのプロパディに対して値を設定しないとエラー page = 125 } } var hoge = book() print(hoge.name, hoge.page)複数個のイニシャライザ
引数やラベル名を変えて複数定義できる。
struct Book { var name : String var page : Int init() { name = "hoge book" page = 125 } init(n: String, p: Int) { name = n page = p } } var hoge = book() print(hoge.name, hoge.page) //hoge book 125 var dogBook = book(n: "dog book", p: 1) print(dogBook.name, dogBook.page) //dog book 1ネスト型(付属型)
ネスト型 : ある構造体、(クラス、列挙型)を構成、使用するために、その構造体と密接に関連する型(構造体)を定義する場合、構造体の一部として宣言できる。
struct SimpleBook { var name : String var page : Int init() { name = "hoge book" page = 125 } init(n: String, p: Int) { name = n page = p } } struct SellBook { typealias Book = SimpleBook //上の構造体 var book : Book var price : Int init(name: String, page: Int, price: Int) { book = Book(n: name, p: page) self.price = price //プロパティ名と引数ラベルの名前が被っているのでself必須 } } var hoge = SellBook(name: "hoge book", page: 125, price:50000) print(hoge.book.name, hoge.book.page, hoge.price) //hoge book 125 50000構造体のメソッド
構造体にもメソッドが定義できる。
struct Book { var name : String var page : Int init() { name = "hoge book" page = 125 } init(n: String, p: Int) { name = n page = p } func toMessage() -> String{ "\(name) は \(String(page)) ページです。" } } var hoge = Book() print(hoge.toMessage()) //hoge book は 125 ページです。構造体の内容を変更するメソッド
Swiftでは同じ構造体のインスタンスを出来るだけメモリで共有しておく方法を採用しているため、構造体の要素が変更されると共有方法も変える必要がある。
構造体の要素を変更するメソッドを定義する際には「mutating」というキーワードをfuncの前に置く。
コンパイラが要素を変更する処理を事前に知ることで、定数に格納されたインスタンに対しての呼び出し禁止、効率のいい実行コードの作成ができる。struct Book { var name : String var page : Int var price : Int init() { name = "hoge book" page = 125 price = 10000 } init(name: String, page: Int, price: Int) { self.name = name self.page = page self.price = price } mutating func sell(b: Bool) { if b { price = Int(Double(price) * 0.8) } else { price = Int(Double(price) * 1.1) } } } var hoge = Book() print(hoge.price) hoge.sell(b: true) print(hoge.price) let fuga = Book() fuga.sell(b: true) //errorタイプメソッド
これまで定義してきたメソッドはインスタンスに対してのみ使用できる。「インスタンスメソッド」に相当するものである。
struct Hoge{ func add(_ a:Int, _ b:Int) -> Int{ a + b } } let hoge = Hoge() print(hoge.add(3,2)) // 5 print(Hoge.add(3,2)) // errorこれに対して、クラスメソッドに相当するメソッドを「タイプメソッド」という。
宣言するときはfunc の前にstaticをつけるstruct Hoge{ static func add(_ a:Int, _ b:Int) -> Int{ a + b } } let hoge = Hoge() print(hoge.add(3,2)) // error print(Hoge.add(3,2)) // 5イニシャライザの中でメソッドを使いたい
イニシャライザの中ではプロパティの初期設定が全て完了していないと、インスタンスメソッドは使えない。
struct Book { var name : String var page : Int var message : String init() { name = "hoge book" page = 125 message = self.toMessage(n: name, p: page) // error } func toMessage(n name:String, p page:Int) -> String{ "\(name) は \(String(page)) ページです。" } }一つの方法としてタイプメソッドで定義すれば実装できる。
struct Book { var name : String var page : Int var message : String init() { name = "hoge book" page = 125 message = Self.toMessage(n: name, p: page) //Book.toMessageとも書ける } static func toMessage(n name:String, p page:Int) -> String{ "\(name) は \(String(page)) ページです。" } } var hoge = Book() print(hoge.message) //hoge book は 125 ページです。構造体のプロパティ
Swiftのプロパティの種類
格納型プロパティ : 定数や変数が機能を提供する
計算型プロパティ : 値の参照と更新機能手続で構成
タイププロパティ(静的プロパティ) : 型(構造体名)に関連する情報を示すプロパティ(どんなインスタンスにも共通の値をセットするといい。メモリの節約になる。)タイププロパティ
struct Book { static var name : String = "hoge book" static var page = 125 } let hoge = Book() print(hoge.name, hoge.page) // error print(Book.name, Book.page) //hoge book 125格納型プロパティの初期値を式で設定
インスタンスを生成するたびにnumberプロパティがインクリメントされる。
var serialNomber: Int = 0 struct Book { var name : String var page : Int let nomber : Int = Book.setNo() init() { name = "hoge book" page = 125 } static func setNo() -> Int{ serialNomber += 1 return serialNomber } } var book1 = Book() print(book1.nomber) // 1 var book2 = Book() print(book2.nomber) // 2 var book3 = Book() print(book3.nomber) // 3計算型プロパティ
他のプロパティの値を参照、計算して返す、代入することができる。
struct Book { var name: String = "hoge" var price: Int var tax : Int { get{ Int(Double(price) * 1.1) } set{ self.price = newValue } } } var hoge = Book(price: 2500) print(hoge.name, hoge.price, hoge.tax) // hoge 2500 2750 hoge.tax = 3400 print(hoge.name, hoge.price, hoge.tax) // hoge 3400 3740setを省略する場合getの記述を省略してかける。
getだけを記述することは出来ない。struct Book { var name: String = "hoge" var price: Int var tax : Int { Int(Double(price) * 1.1) } } var hoge = Book(price: 2500) print(hoge.name, hoge.price, hoge.tax) // hoge 2500 2750 hoge.price = 3400 //hoge.taxは使えなくなるので注意 print(hoge.name, hoge.price, hoge.tax) // hoge 3400 3740特殊な設定
getにmutatingを記述 : プロパティの値を変えることができる。
struct Book { var name: String = "hoge" var price: Int var tax : Int { mutating get{ //getでプロパティ値を変えるときに記述 price = Int(Double(price) * 1.1) } set{ self.price = newValue } }setにnonmutating : 背tで値を変更しないとき、構造体を定数に入れててもsetを呼べるようにする。
struct Book { var name: String = "hoge" var price: Int var tax : Int { get{ Int(Double(price) * 1.1) } nonmutating set{ print(newValue) } } let hoge = Book(price: 2500) hoge.tax = 3400 //構造体が定数に格納されててもsetを呼べるプロパティオブザーバ
プロパティオブザーバ : 格納型プロパティの値が変更されるのをきっかけに
起動できる処理struct Book { var name: String = "hoge" var price: Int var tax : Int { willSet{ print("new : \(newValue)") // new : 108 } didSet{ print("old : \(oldValue)") // old : 110 } } } var hoge = Book(price: 100, tax: 110) hoge.tax = 108添字付け
添字付け:複数のプロパテがあるときに配列のように添字を使ってアクセスできるようにする方法
struct BookShelf { var book = ["hoge", "fuga", "inu"] var comic = ["JUMP", "oni"] var count = 5 subscript(i: Int) -> String { return i < 3 ? book[i] : comic[i-3] } } let list = BookShelf() for i in 0 ..< list.count { print(list[i],terminator: " ") // hoge fuga inu JUMP oni }
- 投稿日:2019-12-04T09:42:24+09:00
Swiftの構造体
趣味でIOSアプリ開発をかじっていた自分が、改めてSwiftを勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。
参考文献
この記事は以下の書籍の情報を参考にして執筆しました。
構造体の初期化
カスタムイニシャライザ : 構造体ごとに定義できる独自のイニシャライザ
カスタムイニシャライザが定義されてないとき、自動的にシンプルなイニシャライザが使える。既定イニシャライザ : 構造体の各プロパティに初期値が指定されている場合、型名(構造体の名前)()と書くことでインスタンスを生成できる。
struct Book { var name = "hoge book" var page = 125 } var fuga = Book() print(fuga.name, fuga.page) // hoge book 125全項目イニシャライザ : 各プロパディ名を引数として書き連ねる。
var dogBook = Book(name: "dog book") print(dogBook.name, dogBook.page) // dog book 125 var oneBook = Book(page: 1) print(oneBook.name, oneBook.page) // hoge book 1全項目イニシャライザを使用する場合はプロパティに初期値を設定する必要がない。
その代わりに初期値が設定されていないプロパティは必ず引数として値を渡す必要がある。struct Book { var name : String var page = 125 } var fuga = Book(name: "hoge book") // hoge book 125 print(fuga.name, fuga.page) var dogBook = Book(name: "dog book") // dog book 125 print(dogBook.name, dogBook.page) var oneBook = Book(page: 1) // error定数に構造体を代入した場合
定数に代入された構造体のインスタンスは、各プロパディの値を変更できない。
この制約は構造体が値型であるためであり、クラスのインスタンスとは違う。(クラスのインスタンスは後ほど勉強します。)struct Book { var name : String var page = 125 } var hoge = Book(name: "hoge book") hoge.name = "fuga book" print(hoge.name, hoge.page) // fuga book 125 let fuga = Book(name: "hoge book") fuga.name = "fuga book" // error構造体に定数プロパティを宣言する場合
最初の一回しか値をセットできない。
struct Book { let name : String = "book" var page = 125 } var hoge = Book(name: "hoge book") // errorstruct Book { let name : String var page = 125 } var hoge = Book(name: "hoge book") hoge.name = "fuga book" //errorカスタムイニシャライザ
initを使って宣言
struct Book { var name : String var page : Int init() { name = " hoge book" //全てのプロパディに対して値を設定しないとエラー page = 125 } } var hoge = book() print(hoge.name, hoge.page)複数個のイニシャライザ
引数やラベル名を変えて複数定義できる。
struct Book { var name : String var page : Int init() { name = "hoge book" page = 125 } init(n: String, p: Int) { name = n page = p } } var hoge = book() print(hoge.name, hoge.page) //hoge book 125 var dogBook = book(n: "dog book", p: 1) print(dogBook.name, dogBook.page) //dog book 1付属型
ネスト型 : ある構造体、(クラス、列挙型)を構成、使用するために、その構造体と密接に関連する型(構造体)を定義する場合、構造体の一部として宣言できる。
struct SimpleBook { var name : String var page : Int init() { name = "hoge book" page = 125 } init(n: String, p: Int) { name = n page = p } } struct SellBook { typealias Book = SimpleBook //上の構造体 var book : Book var price : Int init(name: String, page: Int, price: Int) { book = Book(n: name, p: page) self.price = price //プロパティ名と引数ラベルの名前が被っているのでself必須 } } var hoge = SellBook(name: "hoge book", page: 125, price:50000) print(hoge.book.name, hoge.book.page, hoge.price) //hoge book 125 50000構造体のメソッド
構造体にもメソッドが定義できる。
struct Book { var name : String var page : Int init() { name = "hoge book" page = 125 } init(n: String, p: Int) { name = n page = p } func toMessage() -> String{ "\(name) は \(String(page)) ページです。" } } var hoge = Book() print(hoge.toMessage()) //hoge book は 125 ページです。構造体の内容を変更するメソッド
Swiftでは同じ構造体のインスタンスを出来るだけメモリで共有しておく方法を採用しているため、構造体の要素が変更されると共有方法も変える必要がある。
構造体の要素を変更するメソッドを定義する際には「mutating」というキーワードをfuncの前に置く。
コンパイラが要素を変更する処理を事前に知ることで、定数に格納されたインスタンに対しての呼び出し禁止、効率のいい実行コードの作成ができる。struct Book { var name : String var page : Int var price : Int init() { name = "hoge book" page = 125 price = 10000 } init(name: String, page: Int, price: Int) { self.name = name self.page = page self.price = price } mutating func sell(b: Bool) { if b { price = Int(Double(price) * 0.8) } else { price = Int(Double(price) * 1.1) } } } var hoge = Book() print(hoge.price) hoge.sell(b: true) print(hoge.price) let fuga = Book() fuga.sell(b: true) //errorタイプメソッド
これまで定義してきたメソッドはインスタンスに対してのみ使用できる。「インスタンスメソッド」に相当するものである。
struct Hoge{ func add(_ a:Int, _ b:Int) -> Int{ a + b } } let hoge = Hoge() print(hoge.add(3,2)) // 5 print(Hoge.add(3,2)) // errorこれに対して、クラスメソッドに相当するメソッドを「タイプメソッド」という。
宣言するときはfunc の前にstaticをつけるstruct Hoge{ static func add(_ a:Int, _ b:Int) -> Int{ a + b } } let hoge = Hoge() print(hoge.add(3,2)) // error print(Hoge.add(3,2)) // 5イニシャライザの中でメソッドを使いたい
イニシャライザの中ではプロパティの初期設定が全て完了していないと、インスタンスメソッドは使えない。
struct Book { var name : String var page : Int var message : String init() { name = "hoge book" page = 125 message = self.toMessage(n: name, p: page) // error } func toMessage(n name:String, p page:Int) -> String{ "\(name) は \(String(page)) ページです。" } }一つの方法としてタイプメソッドで定義すれば実装できる。
struct Book { var name : String var page : Int var message : String init() { name = "hoge book" page = 125 message = Self.toMessage(n: name, p: page) //Book.toMessageとも書ける } static func toMessage(n name:String, p page:Int) -> String{ "\(name) は \(String(page)) ページです。" } } var hoge = Book() print(hoge.message) //hoge book は 125 ページです。構造体のプロパティ
Swiftのプロパティの種類
格納型プロパティ : 定数や変数が機能を提供する
計算型プロパティ : 値の参照と更新機能手続で構成
タイププロパティ(静的プロパティ) : 型(構造体名)に関連する情報を示すプロパティ(どんなインスタンスにも共通の値をセットするといい。メモリの節約になる。)タイププロパティ
struct Book { static var name : String = "hoge book" static var page = 125 } let hoge = Book() print(hoge.name, hoge.page) // error print(Book.name, Book.page) //hoge book 125格納型プロパティの初期値を式で設定
インスタンスを生成するたびにnumberプロパティがインクリメントされる。
var serialNomber: Int = 0 struct Book { var name : String var page : Int let nomber : Int = Book.setNo() init() { name = "hoge book" page = 125 } static func setNo() -> Int{ serialNomber += 1 return serialNomber } } var book1 = Book() print(book1.nomber) // 1 var book2 = Book() print(book2.nomber) // 2 var book3 = Book() print(book3.nomber) // 3計算型プロパティ
他のプロパティの値を参照、計算して返す、代入することができる。
struct Book { var name: String = "hoge" var price: Int var tax : Int { get{ Int(Double(price) * 1.1) } set{ self.price = newValue } } } var hoge = Book(price: 2500) print(hoge.name, hoge.price, hoge.tax) // hoge 2500 2750 hoge.tax = 3400 print(hoge.name, hoge.price, hoge.tax) // hoge 3400 3740setを省略する場合getの記述を省略してかける。
getだけを記述することは出来ない。struct Book { var name: String = "hoge" var price: Int var tax : Int { Int(Double(price) * 1.1) } } var hoge = Book(price: 2500) print(hoge.name, hoge.price, hoge.tax) // hoge 2500 2750 hoge.price = 3400 //hoge.taxは使えなくなるので注意 print(hoge.name, hoge.price, hoge.tax) // hoge 3400 3740特殊な設定
getにmutatingを記述 : プロパティの値を変えることができる。
struct Book { var name: String = "hoge" var price: Int var tax : Int { mutating get{ //getでプロパティ値を変えるときに記述 price = Int(Double(price) * 1.1) } set{ self.price = newValue } }setにnonmutating : 背tで値を変更しないとき、構造体を定数に入れててもsetを呼べるようにする。
struct Book { var name: String = "hoge" var price: Int var tax : Int { get{ Int(Double(price) * 1.1) } nonmutating set{ print(newValue) } } let hoge = Book(price: 2500) hoge.tax = 3400 //構造体が定数に格納されててもsetを呼べるプロパティオブザーバ
プロパティオブザーバ : 格納型プロパティの値が変更されるのをきっかけに
起動できる処理struct Book { var name: String = "hoge" var price: Int var tax : Int { willSet{ print("new : \(newValue)") // new : 108 } didSet{ print("old : \(oldValue)") // old : 110 } } } var hoge = Book(price: 100, tax: 110) hoge.tax = 108添字付け
添字付け:複数のプロパテがあるときに配列のように添字を使ってアクセスできるようにする方法
struct BookShelf { var book = ["hoge", "fuga", "inu"] var comic = ["JUMP", "oni"] var count = 5 subscript(i: Int) -> String { return i < 3 ? book[i] : comic[i-3] } } let list = BookShelf() for i in 0 ..< list.count { print(list[i],terminator: " ") // hoge fuga inu JUMP oni }
- 投稿日:2019-12-04T08:22:19+09:00
Appleの機械学習がヤバい
近年、機械学習はさまざまな分野で利用され注目を高めてきました。また、Firebaseをはじめてとした様々なクラウドサービスで手軽に利用できるほど便利なものになりました。そんな中、今年発表されたAppleの機械学習フレームワークCoreML3がすごかったため、今回CoreML周辺の情報を簡単にですがまとめてみました。
CoreMLとは?
Appleが2017年に発表した機械学習を扱うためのフレームワークであり、今年新たにCoreML3という名前で強力な機能の追加がなされた。機械学習モデルの仕様はオンデバイスで完結させることができ、さらに
CreateML
というFrameworkを使い機械学習モデルを超簡単に作成することが可能。モデルを作成するためのCreateMLアプリはこちら
下記の画像でCoreMLの上位レイヤー部分にあたるフレームワークがCoreML3にのFeatuesです。参照: https://developer.apple.com/documentation/coreml より
CoreMLを使用するメリットは?
CoreMLを使用するメリットは下記の通り大きく分けて4つあり、どれもオンデバイスで完結することによる効力を受けています。
Privacy
- ネットワークを介してデータのやりとりする必要がないため、個人情報を守ることができる
Speed
- デバイス上ですでにある学習モデルを使用するため、データ取得にかかる時間が超高速
No Server
- デバイス上で完結するためサーバが必要なくなります
Available
- トレーニング済みモデルをCoreMLに変換することも可能なため(keras -> CoreML)、自作Modelを有効的に使うことができる(https://developer.apple.com/jp/documentation/coreml/converting_trained_models_to_core_ml/)
CoreMLで何ができる?
実際にアプリにCoreMLが取り入れられているものを参考に見ていきましょう
これは、写真を撮るだけでブーツの種類を判別し、そのブーツを販売しているオンラインストアを探すことができるアプリですが、ネットワークを介していないため、普通にサーバを介してデータを取得する場合とでは処理の速さは一目瞭然です。一度試しに使って見るとわかるのですが、超高速です。
このアプリはバスケの練習を機械学習を用いることで、より効果的にトレーニングを行うことができるアプリです。
どこで機械学習が使われている?
- バスケのドリブルを練習したい場合、ボール・人物・人物のバランスを検知し、トレーニングしたいポイントに応じてバーチャルな目標物が画面に出力され。目標物をタッチすることでトレーニングできる仕組み
どう作られてる?
下記の項目が主に使われている機能です。ここではだいたい機能が
Visio
フレームワークを使うことで実現されています。このVisionフレームワークについては別のセクションでも説明します。
- ObjectDetection(物体検知) -> Vision-framework(https://developer.apple.com/documentation/vision/recognizing_objects_in_live_capture)
- ObjectTracking(物体追跡) -> Vision-framework(https://developer.apple.com/documentation/vision/tracking_multiple_objects_or_rectangles_in_video)
- PoseEstimation(姿勢検知) -> https://github.com/tucan9389/PoseEstimation-CoreML <- CoreMLを使った姿勢検知機能。Appleから提供されているModelは見つかりませんでした、、
- HitDetection(衝突検知) -> Vision-framework
CoreML-Framework
- Vision(https://developer.apple.com/documentation/vision)
- FaceDetection 顔をリアルタイムで認知し、トラッキングすることができる(https://developer.apple.com/documentation/vision/tracking_the_user_s_face_in_real_time)
- FaceLandmarks 顔の76pointの位置を検知することで、細かな表情の変化や瞳の向いている方向などを取得することができる(https://developer.apple.com/documentation/vision/vnfacelandmarks2d)
- ObjectTracking ビデオ内で複数のオブジェクトをトラッキングすることができる -> トラック競技などで選手などをそれぞれトラッキングすることなどが可能(https://developer.apple.com/documentation/vision/tracking_multiple_objects_or_rectangles_in_video)
- NaturalLanguage(https://developer.apple.com/documentation/naturallanguage)
- 言語判定
- MLTextClassifierを用いてカスタムモデルを作ることも可能
- textがポジティブな内容かネガティブな内容かなどの判断をすることができる(カスタムModelを作ればさらに細分化して感情を判断することができる)
- Word Embeddings ワードの類似語を取得するsuggestions機能を簡単に実装することができる(https://www.hackingwithswift.com/example-code/naturallanguage/how-to-find-similar-words-for-a-search-term)(https://developer.apple.com/documentation/naturallanguage/nlembedding)
- SoundAnalysis(https://developer.apple.com/documentation/soundanalysis)
- MLSoundClassifierを用いてカスタムモデルを作ることも可能
- 笑っている声や泣いている声を判断することができる
- 水の音、車の音、鳥の鳴き声など普段耳にしているような音も判別することができる
- 活用の例 ?
- カメラアプリなどで人物が笑っている時などに自動でシャッターを切ることができるなど
- CreateML(https://developer.apple.com/documentation/createml)
- Image, Sound, Textなど様々なデータを学習させModelを作成することのできるフレームワーク
- TableDataも学習させることができる※番外編で活用例紹介(https://developer.apple.com/documentation/createml/mldatatable)
- これ以外にも様々な機械学習用のFrameworkは数多くありますが、全て紹介することはできないため今回はこのぐらいで?♂️
Model Creation
- MLModel作成用のCreateML-Appで簡単に作ることができる(Xcodeでも可能)
Model生成の流れ
- DataCollection
- DataPreparation
- Training
- Testing
MLModelを作る上でポイント
Modelの精度の高まりを認知し、効果的にトレーニングするために、トレーニングの最初からテストを行って行くことが重要です。また、アプリ毎の用途に応じて必要なデータ量などは変わっていきますが
ImageClassification
の場合1クラス10枚から学習することが可能でしたので、手軽に使えそうです。そして、自分は機械学習についての知識は浅かったため知らなかったのですが、無視するべき関係のないデータ(ネガティブクラス)を含めることもModelを作成する上では重要みたいです。なぜ関係のないデータも必要かというと、例えば、りんご・いちごしか学習クラスがなかった場合、画像認識の際にどちらかのカテゴリーに結果が振り分けられますが、他のデータも学習させておくことで間違いの結果に当てはめてしまう確率を下げるということが狙いみたいですね。また、クラス(カテゴリー)のTrainingデータ量のバランスも大事で、できるだけバランスよく均等にデータ量を整えることが質の高いModelを作る上では重要です。最後に
今回はあまり触れていませんでしたが、個人的に
MLDataTable
の活用の幅が広そうだなと感じました。例えば、好みの条件に合わせた地域の家賃相場を算出するアプリなどで、地域
・間取り
・キッチンの広さ
などをそれぞれテーブルのカラムとし、それぞれの家賃を学習させることで、条件にあった地域の家賃がどのくらいなのかをネットワークを介さず高速に算出することができます。このようにCoreMLには色々な活用方法があり、まだまだ便利な機能があると思うので、何か面白そうな機能を見つけたらぜひ教えてください?
- 投稿日:2019-12-04T02:26:33+09:00
TabBarControllerをStoryboardReferenceを利用して導入してみた(Storyboardを分ける)
状況
Youtubeチャンネルの素材用にTinderの模擬アプリを作成する際、
TabBarController
を使ったのですが
やり方が色々あってこんがらがったのと、いくつかつまづいたのでメモ。やりたいこと
TabBarController
をStoryboardReference
を利用して導入する
->StoryboardをTabBarController
,遷移先のVC
(Tabの数ぶんStoryboardを用意 or 遷移先を1つのStoryboardにまとめる)でそれぞれ分けたいのでStoryboardReference
を利用
->遷移先のVCにNavigationController
を継承させたい対策
TabBarController.storyboard
を新しく作る->TabBarController
を新しく作って、つながっている2つのVCを消しておくStoryboardReference
をTabの数だけ作る- 各々の
StoryboardReference
をTabと紐付けたい遷移先のVCと紐付ける- TabBarControllerの
is initial viewcontroller
にチェックを入れるTARGET
のMain interface
をTabBarControllerが存在するstoryboardに設定info.pist
のStoryboard Name
をTabBarControllerが存在するstoryboardに設定- 以上を行ってもTabが表示されない場合はcleanや再起動諸々を試す(info.plistとかをいじるとcleanとかで解決するパターンが結構あるらしい。僕自身もcleanして解決しました。)
![]()
※一番左のタブから順々に紐づいていく
- 遷移先のVCごとに
Storyboard
がある場合:
StoryboardReference
を選択しShow the Attributes inspector
->Storyboard reference
->Storyboard
にStoryboardの名前(ファイル名)を入力(Storyboard ID
ではない)
- 遷移先のVCを1つのStoryboard
にまとめている場合:
上記に加えて、VCをStoryboard IDから参照が必要->Show the Identity inspector
->Identity
->Storyboard ID
に入力
- control+右クリックで各々の
StoryboardReference
に繋ぐ->Relationship Segue
のview controllers
を選択 (※Modal
などではない)- 遷移先のVCで
Is Initial Viewcontroller
の設定を確認(NCが紐づいているならそちらに設定する)- 遷移先のVCで
Is Initial Viewcontroller
に設定したものにTabBar Item
を挿入以上。
参考
・[Xcode7] 「Unknown class xxxxx in Interface Builder file.」が出る - Qiita
- 投稿日:2019-12-04T01:48:06+09:00
【Swift】Arrayからユニークな順序付きリストへの変換を超絶シンプルに書く
SwiftでArrayから重複なしの順序を保ったままのリストを生成したい!
と思って調べてみると、この辺の記事が参考になりますが、reduce()を使って愚直に全てをなめていく実装が散見されます
今回はもっとスマートさを求めてシンプルに書けないか考えていきます
(よりシンプルに書くことに特化しているのでパフォーマンスは気にしてません)
Setは使える?
SwiftにはSetがあります。が、答えはノーです。
なぜなら、Setは重複しない順序を持たないリストだからです。
やってみます。let array: [String] = ["hoge", "fuga", "hoge", "piyo", "piyo"] // 最初に出てきた順でユニークになってほしい -> ["hoge", "fuga", "piyo"] print(Set(array)) // "["fuga", "piyo", "hoge"]\n" print(Set(array)) // "["piyo", "hoge", "fuga"]\n" print(Set(array)) // "["piyo", "hoge", "fuga"]\n"毎回結果が変わってしまいました。これでは順序を保つという条件が保証できません
順序付きのSetってないの?
Swift用はSet一択のようで、存在しないようです。
ですが、Foundation FrameworkにはNSOrderedSetという昔ながらのImmutableな順序付きSetのクラスが現在もサポートされています。NSOrderedSetを使って順序付きのユニークリストを作る
さっそく書いていきましょう。
import Foundation extension Array { func unique() -> [Self.Element] { return NSOrderedSet(array: self).array as! [Self.Element] } }こんな感じに書けるかと思います。
どうでしょう、めちゃくちゃシンプルですよね
NSOrderedSetに入れて吐き出すだけです!NSOrderedSetはObjective-C時代のものなので、インターフェースがNSArray時代のように
[Any]
となってしまいます。
そのため、入力時も[Any]
として扱われ、出力ももちろん[Any]
として返ってきてしまいます。そこで、SwiftのArrayのextensionとしてメソッドを用意し、Elementの型を入出力で縛ってあげることによって、使用する側は型安全でSwiftらしく書けるようにしています。
(as!
のような汚い実装はこうしてextensionの中に隠蔽してしまいましょう)
では、最後に結果を確認していきます。
let array: [String] = ["hoge", "fuga", "hoge", "piyo", "piyo"] print(array.unique()) // "["hoge", "fuga", "piyo"]\n" print(array.unique()) // "["hoge", "fuga", "piyo"]\n" print(array.unique()) // "["hoge", "fuga", "piyo"]\n"いかがでしょうか?
呼び出しもシンプルで今回の目的を達成することができました
こちらは順序をサポートしているので、何度実行しても結果は変わりません!P.S.
- Advent Calendarに登録しようと思っていたのにすっかり忘れてしまったので、普通の投稿です(笑)
- 投稿日:2019-12-04T01:02:32+09:00
RxSwift MVVM における ViewModel 設計
自分は RxSwift で ViewModel はどうやって設計していくか下記三つ分けて書いていこうと思います。
- Immutable
- テストのしやすさ
- プロトコルの読み取りやすさ
Immutable
- 可変を避けるため struct を使う。
- Observable, PublishSubject など値が流れ流けど不可変にする。
- Protocol には var で定義されるが、struct では let で宣告します。
テストのしやすさ
テストしやすく、 mock しやすくするため、 外部リソースはできれば全て DI できるようにします。
例えばデータ処理、API 叩きなどが書いてある struct や class らはコンストラクタの引数で渡します。
そして、 ViewModel 自身の中身に、全域的なオブジェクトやクラスを直接使用すること、できればしないようにします。プロトコルの読み取りやすさ
- インプットとアウトプットを分けて定義する
RxSwift コンポーネントの使い分け
- インプットは PublishSubject を使う
- アウトプットは Driver を使う
- 出口は UI なので Driver の方が良い
- Driver はコンストラクタで初期化する
実装例
import RxSwift import RxCocoa protocol MyViewModelInput { var titleChanged: PublishSubject<String?> { get } var contentChanged: PublishSubject<String?> { get } } protocol MyViewModelOutput { var titleLength: Driver<Int> { get } var contentLength: Driver<Int> { get } } typealias MyViewModelType = MyViewModelInput & MyViewModelOutput struct MyViewModel: MyViewModel { let titleChanged = PublishSubject<String?>() let contentChanged = PublishSubject<String?>() let titleLength: Driver<Int> let contentLength: Driver<Int> init() { titleLength = titleChanged .map { title in guard let title = title else { return 0 } return title.count } .asDriver { _ in .empty() } contentLength = contentChanged .map { content in guard let content = content else { return 0 } return content.count } .asDriver { _ in .empty() } } }ViewModel 以外は?
アウトプットがある場合、相手は UI 出なければ Observable や Single でしておいて大体問題ないと思います。
結
わかりにくい、説明不足ところや間違えたところあればぜひコメント欄にコメントしてください。