- 投稿日:2021-02-20T21:51:30+09:00
【Swift】タプル型の便利な使い方(備考録)
導入
Swiftを使っていて、これまでタプル型を自分で実装して使う機会がなかったんですが、
開発していたらタプル型ってこういう時に使えばいいんだという気づきがあったため記事を書きました。僕はMVPモデルでアプリ開発をしていた時に、タプル型の利便性に気付かされたので、今回はMVPモデルで作ったサンプルアプリを元に解説します。
※あまりに簡素なサンプルアプリであり、Modelは不要なので今回は、ViewとPresenterのみを使用します。■ MVPモデルについてはこちらの記事が大変参考になりました
https://qiita.com/hicka04/items/25be38a90fdde29c97c2サンプルアプリについて
下記に画面のキャプチャを載せました。
「計算ボタン」をタップすると、画面上に書かれている足し算を行い、水色の枠の部分に答えを表示します。
ありえないくらい簡素なサンプルアプリでございます。ソースコード
ちなみに、今回やろうとしている処理は
calculate()
がViewControllerから引数を受ける形にすればいいのですが、今回はタプル型を使いたいため、あえて引数を渡さないようにしています。
もっと複雑なアプリを作る場合、Presenter内の処理で、ViewControllerから引数として渡された値以外も、ViewControllerから取得して使いたいということがあると思います。
今回はそういったケースを想定しております。MVPアーキテクチャを採用する場合、PresenterはViewが持っているLabelなどを直接参照したり、更新したりすることなく、
PresenterProtocol
で定義されたメソッドを通して、値の取得やViewへの更新依頼を行います。今回は、
getAdditionalNumber()
を用いて、ViewControllerの値を取得しています。■タプル型を活用した場合
1つのメソッドでViewControllerの2つの値を取得することが可能です。今回は、足し算に必要な2つの数を1つのメソッドで取得できています。
あとはPresenterで使いたい値を自由に取り出すだけで、簡単に処理できます。
今回は、calculateTuple()
を呼び出した場合の処理になります。■タプル型を使わない場合
逆に、タプル型を使わない場合、各ラベルを取得するのに別々のメソッドを書かないといけないので、
PresenterProtocol
に定義しないと行けないメソッドが増殖してしまいます。
そのたびにメソッドを増やさないといけないのは手間ですし、無駄なコードを書かないと行けないかなと思います。
今回は、calculate()
を呼び出した場合の処理になります。ViewController.swiftclass ViewController: UIViewController { @IBOutlet weak var labelNum1: UILabel! @IBOutlet weak var labelNum2: UILabel! @IBOutlet weak var labelAnswer: UILabel! var presenter: Presenter? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. presenter = Presenter(self) } @IBAction func touchCalculator(_ sender: Any) { // タプル型を使わない場合の処理を実行 // presenter?.calculate() // タプル型を使う場合の処理を実行 presenter?.calculateTuple() } } extension ViewController: PresenterProtocol { func updateAnswerLabel(answer: Int) { labelAnswer.text = "\(answer)" } // タプル型を使わない場合 func getAdditionalNumber1() -> String { return labelNum1.text ?? "" } func getAdditionalNumber2() -> String { return labelNum2.text ?? "" } // タプル型を使う場合 func getAdditionalNumber() -> (num1: String, num2: String) { return (labelNum1.text ?? "", labelNum2.text ?? "") } }Presenter.swiftimport Foundation protocol PresenterProtocol: AnyObject { func updateAnswerLabel(answer: Int) -> Void // タプル型を使わない場合 func getAdditionalNumber1() -> String func getAdditionalNumber2() -> String // タプル型を使う場合 func getAdditionalNumber() -> (num1: String, num2: String) } class Presenter { weak var view: PresenterProtocol? init(_ view: PresenterProtocol) { self.view = view } func calculate() { let _num1 = view?.getAdditionalNumber1() let _num2 = view?.getAdditionalNumber2() guard let num1 = _num1, let num2 = _num2 else { return } let sum = Int(num1)! + Int(num2)! view?.updateAnswerLabel(answer: sum) } func calculateTuple() { let tuple = view?.getAdditionalNumber() guard let num1 = tuple?.num1, let num2 = tuple?.num2 else { return } let sum = Int(num1)! + Int(num2)! view?.updateAnswerLabel(answer: sum) } }まとめ
今回は、タプル型の使い所について、自分の備考録を兼ねて書いてみました。
サンプルアプリが簡素だったため、メリットが捉えにくかったかもしれませんが、
「似たような属性の値を複数以上(配列にするほど長くもない程度)取得したい場合に、メソッドの戻り値として使えるよ」
ということが伝わっていればいいなと思います。。。
- 投稿日:2021-02-20T19:12:55+09:00
JenkinsのBuild AgentをMac上に構築する
Jenkinsを使ったiOSアプリのCI/CD環境を構築するために、ビルドエージェントとしてmac miniを登録する機会があったため手順をまとめておきます。
基本的な流れは GitLab CI Runner に近いです。
https://qiita.com/takamii228/items/64fd6879e6acce845980なお今回利用したJenkinsのバージョンは 2.235.2 です。
mac miniの設定をする
まずmac miniでJenkinsのAgentが動作する準備をします。
JenkinsのAgentを動作させるためにはJavaが必要なのでHomeBrew経由でインストールします。
$ brew install openjdk@11 $ export PATH="/usr/local/opt/openjdk@11/bin:$PATH" $ java -version openjdk version "11.0.9" 2020-10-20 OpenJDK Runtime Environment (build 11.0.9+11) OpenJDK 64-Bit Server VM (build 11.0.9+11, mixed mode)またJenkins Agentが動作するための作業ディレクトリを切って移動しておきましょう。
$ mkdir ~/jenkins $ cd ~/jenkins今回はサンプル用にかんたんなシェルスクリプトを動作させますが、本来はmac miniのagent上で実行したいCI・CDの実行環境も合わせて構築する必要があります。
JenkinsのJNLPのポートを固定する
JenkinsのAgentの通信方法はsshを使うものとJNLPを使うものを選べますが今回はJNLPを使います。
JNLPはJava Network Launch Protocolの略でJenkinsのビルドエージェントを動作させる仕組みとして用いられています。詳細な説明はこちらを参照してください。https://docs.oracle.com/javase/tutorial/deployment/deploymentInDepth/jnlp.html
Jenkinsのデフォルトの設定だとJNLPで使うポート番号はランダムになるようになっています。ランダムだとセキュアでよいのですが、IPやポートに制限を加えている環境での利用では可変だと困るので、適当な値に固定にしておくとよいでしょう。他のポートとぶつからないようにプライベートポートの範囲である49152 ~65535の間から一つ選びましょう。
JNLPで利用するポート番号の固定はJenkinsにログインして管理画面(Manage Jenkins)に移動してグローバルセキュリティ(Configure Global Security)のAgentの設定で指定できます。
合わせてこのポート番号とmac miniからの通信経路の穴あけを確認しておきましょう。
JenkinsのBuild Agentを追加する
次にJenkinsの管理画面でBuild Agentのノード設定を行います。
管理画面(Manage Jenkins)に移動して「ノード管理(Manage Nodes and Clouds)」を選択します。
ノードの管理画面の左のリストのうち「新規ノードの作成(New Node)」を選択して新しいNodeを作成します。ノードの名前を入力し、Permanent AgentにチェックをいれてOKを押します。
次に詳細な設定を入力していきます。
- ノード名(Name)
- ノードの名前を入力します
- 説明(Description)
- ノードの説明を入力します
- 同時ビルド数(# of executors)
- 同一ノード上での同時ビルド数を入力します。iOSは並行でビルドするとDerivedDataの関係でエラーになる可能性があるので1にします
- リモートFSルート(Remote root directory)
- mac mini上での絶対パスをします。今回は
/Users/${username}/jenkins
です- ラベル(Labels)
- Jenkinsfileでnodeとして指定するラベルを定義します
- 用途(Usage)
- 指定された場合のみ動作するか共有にするか選べます。今回は指定された場合のみに設定します。
- 起動方法(Launch method)
- Launch agent by connection it to the master にします
- 可用性(Availability)
- Keep this agent online as much as possible にします
- ノードプロパティ
- 特に設定しません
すべて入力したら保存(Save)します。
Jenkins Agentを起動してMasterと接続する
次にmac mini上でagentを起動してJenkins Masterと接続します。
動作させるJenkins Agent用のjarファイルは
http://yourserver:port/jnlpJars/agent.jar
にアクセスするとダウンロードすることができます。yourserver:port
の部分は自身の動作させている環境に合わせて修正してください。先程作成したノードの作成画面にagent.jarの起動コマンドが記載されているので、それをmac mini上で起動すればよいです。
$ java -jar agent.jar -jnlpUrl http://${yourhostname}:${port}/computer/${jenkins-agent-node-name}/slave-agent.jnlp \ -secret ${secretToken} -workDir "/Users/xxxxxx/jenkins" Feb 18, 2021 8:04:58 PM org.jenkinsci.remoting.engine.WorkDirManager initializeWorkDir INFO: Using /Users/xxxxxx/jenkins/remoting as a remoting work directory Feb 18, 2021 8:04:58 PM org.jenkinsci.remoting.engine.WorkDirManager setupLogging INFO: Both error and output logs will be printed to /Users/xxxxxx/jenkins/remoting Feb 18, 2021 8:04:58 PM hudson.remoting.jnlp.Main createEngine INFO: Setting up agent: ${jenkins-agent-node-name} Feb 18, 2021 8:04:58 PM hudson.remoting.jnlp.Main$CuiListener <init> INFO: Jenkins agent is running in headless mode. Feb 18, 2021 8:04:58 PM hudson.remoting.Engine startEngine INFO: Using Remoting version: 4.3 Feb 18, 2021 8:04:58 PM org.jenkinsci.remoting.engine.WorkDirManager initializeWorkDir INFO: Using /Users/xxxxxx/jenkins/remoting as a remoting work directory Feb 18, 2021 8:04:58 PM hudson.remoting.jnlp.Main$CuiListener status INFO: Locating server among [http://${yourhostname}:${port}/] Feb 18, 2021 8:04:58 PM org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver resolve INFO: Remoting server accepts the following protocols: [JNLP4-connect, Ping] Feb 18, 2021 8:04:58 PM org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver isPortVisible WARNING: Connection refused (Connection refused) Feb 18, 2021 8:04:58 PM org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver resolve INFO: Remoting server accepts the following protocols: [JNLP4-connect, Ping] Feb 18, 2021 8:04:58 PM hudson.remoting.jnlp.Main$CuiListener status INFO: Agent discovery successful Agent address: ${yourhostname} Agent port: ${jnlpport} Identity: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Feb 18, 2021 8:04:58 PM hudson.remoting.jnlp.Main$CuiListener status INFO: Handshaking Feb 18, 2021 8:04:58 PM hudson.remoting.jnlp.Main$CuiListener status INFO: Connecting to ${yourhostname}:${jnlpport} Feb 18, 2021 8:04:58 PM hudson.remoting.jnlp.Main$CuiListener status INFO: Trying protocol: JNLP4-connect Feb 18, 2021 8:04:58 PM hudson.remoting.jnlp.Main$CuiListener status INFO: Remote identity confirmed: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Feb 18, 2021 8:04:58 PM hudson.remoting.jnlp.Main$CuiListener status INFO: Connected上記ログのように Connect が表示されれば起動完了です。
Jenkinsの管理画面からも確認できます。
ジョブを動かしてみる
ジョブが正しく設定されていることを確認してみましょう。Jenkinsでパイプラインジョブを作成して、mac mini上のマシン名を表示するだけのジョブ設定をしたJenkinsfileを入れて実行してみます。labekのところはノードの設定のときに入力したラベル名を入れます。
pipeline { agent { node { label '${nodeLabelName}' } } stages { stage('build'){ steps { sh 'uname -n' } } } }実行ログでmac miniのマシン名が表示されれば設定は完了です。
Started by user xxxxxx Running in Durability level: MAX_SURVIVABILITY [Pipeline] Start of Pipeline [Pipeline] node Running on jenkins-agent-sample in /Users/xxxx/jenkins/workspace/jenkins-connect-sample [Pipeline] { [Pipeline] stage [Pipeline] { (build) [Pipeline] sh + uname -n xxxxxxxxx.local [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline Finished: SUCCESS以上で設定は完了です。実際にはこのシェルディレクティブの中でCIやCDのスクリプトを実行するようにして、Jenkinsのジョブ設定でトリガーやパラメータ設定をすれば完成です。
おまけ
JenkinsのAgent起動ジョブはコマンドラインでjavaコマンドで起動していました。もしmac miniを再起動したりコネクションが切れたときに再接続したい場合は、都度マシンにアクセスして起動コマンドを打たないといけません。
この課題を解決するために、macOSでデフォルトで利用できるlaunchdという仕組みを活用します。launchdについては公式サイトに例が沢山乗っているのでこちらを参考にします。
今回は起動時に起動のjavaコマンドを実行し、ネットワークが再接続されたりクラッシュしたら再度javaコマンドを実行するようにlauchdを設定してみます。
launchdはplistファイルに設定や実行コマンドを記述します。Jenkins Agentを起動するplistファイルは以下のようになります。コマンドは絶対パスで記入するのがポイントのようです。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>local.Jenkins.Agent.launchd</string> <key>KeepAlive</key> <dict> <key>NetworkState</key> <true/> <key>Crashed</key> <true/> </dict> <key>RunAtLoad</key> <true/> <key>ProgramArguments</key> <array> <string>/usr/local/opt/openjdk@11/bin/java</string> <string>-jar</string> <string>/Users/xxxxxx/jenkins/agent.jar</string> <string>-jnlpUrl</string> <string>http://${yourhostname}:${port}/computer/${jenkins-agent-node-name}/slave-agent.jnlp</string> <string>-secret</string> <string>${secretToken}</string> <string>-workDir</string> <string>"/Users/xxxxxx/jenkins"</string> </array> <key>StandardOutPath</key> <string>/tmp/jenkins.agent.stdout</string> <key>StandardErrorPath</key> <string>/tmp/jenkins.agent.stderr</string> </dict> </plist>作成したファイルは
~/Library/LaunchAgents/
配下に配置してlaunchctlコマンドでloadすると設定されます。$ launchctl load ~/Library/LaunchAgents/jenkins.agent.settings.plistこうしておくとmac miniの起動時に自動でagent.jarを起動してくれます。
再起動してみましょう。$ ps aux | grep "agent.jar" xxxxx 969 0.0 2.5 8137224 211276 ?? S 6:51PM 0:19.33 /usr/local/opt/openjdk@11/bin/java -jar /Users/xxxx/jenkins/agent.jar $ launchctl list | grep "local.Jenkins" 969 -9 local.Jenkins.Agent.Setting設定通り起動してくれていますね。
クラッシュしたりネットワークが切れたときについては試してませんがおそらく再起動してくれるはずです。もしかしたらjnlpのレイヤでやってくれるのかもですが、まずはしばらく運用してみようと思います。
参考資料
- 投稿日:2021-02-20T15:05:20+09:00
iOS Simulatorのスクリーンショットがデフォルトでデスクトップに保存されるけど保存先を変えたい
iOS Simulatorを利用中にキーボードのCommand + Sを押すとスクリーンショットが撮影されディスクトップに保存されます。
- Altを押しながらCommand + Sを押すとスクリーンショットの保存先を選択できます
- 次回はCommand + Sだけで指定した保存先に保存されるようです
- デフォルトの保存先にするか決めるチェックボックスが表示される場合もあるらしい
- Command + Control + Cを押すとスクリーンショットがクリップボードにコピーされます
- Excelスクリーンショットエビデンスが高速に作れます
参考
iphone - Take screenshots in the iOS simulator - Stack Overflow
- 投稿日:2021-02-20T10:03:51+09:00
pod install実行時に`target overrides the OTHER_SWIFT_FLAGS build setting defined`が表示され、追加したライブラリがロードできない
環境
Xcode Version 12.4
MacOS BigSur 11.2.1発生した問題
プロジェクトのPodfileにSnapKitを追記し、
pod install
を実行したところ、
Cannot load underlying module for ‘SnapKit'
という問題が発生しました。
追加したライブラリのインポートはできるがロードができていない様子。$ pod install Analyzing dependencies Downloading dependencies Installing SnapKit (5.0.1) Generating Pods project Integrating client project Pod installation complete! There are 12 dependencies from the Podfile and 22 total pods installed. [!] The `MySampleApp [Debug]` target overrides the `OTHER_SWIFT_FLAGS` build setting defined in `Pods/Target Support Files/Pods-MySampleApp/Pods-MySampleApp.debug.xcconfig'. This can lead to problems with the CocoaPods installation - Use the `$(inherited)` flag, or - Remove the build settings from the target.解決方法
[!]
の案内通り、対象の設定に$(inherited)
を追加する。手順
xcodeproj → TARGETS → Build Settings → 右上の検索窓でother_swiftとかで検索すると下のような項目がでる。
※ 特に設定してない場合は画像と違って空欄になっているはず対象スキームの右側の欄をダブルクリックすると下のような画面が開くので、
+
アイコンをクリックして$(inherited)
を項目に追加
これで設定は完了。
再度pod install
するとメッセージが消えて、ライブラリがロードできるようになっていました。説明
設定ファイルの内容を継承(
inherited
)して上書きしていく必要があるそうですが、
今回の場合はその継承関係が途中で切れていたため、正常に反映できていなかったようです。
参考: XCodeのBuild Settingsで、値が決まっていくルール -Qiita
- 投稿日:2021-02-20T07:20:19+09:00
iOSアプリケーションにキーボードショートカットのサポートを追加(UIKitとSwiftUI両方で)
この短い記事では、アプリケーションにキーボードショートカット (keyboardShortcut, UIKeyCommand) 機能を加える方法について説明します。これは、ユーザーがiPadにキーボードを接続した状態でアプリを実行している場合に便利です。
多くの人はiPad用のキーボードカバーを購入しているので、アプリケーションにキーボードショートカットを実装しておくと便利になるでしょう。実装はほんの数分で完了します。
アプリケーションが提供するすべてのショートカットを表示するには、iPadキーボードのコマンド ⌘ キーを長押しします。それにより提供されているショートカット一覧のメニューが表示されます。
SwiftUIのアプリケーションに
修飾キー
keyboardShortcut
を使うことで、SwiftUIのインタラクティブなUI要素に、ショートカットキーを簡単に追加できます。public func keyboardShortcut(_ key: KeyEquivalent, modifiers: EventModifiers = .command) -> some View例えば、ボタンにキーボードショートカットを追加するには:
Button(action: { counter += 1 }, label: { Text("+1") }) .keyboardShortcut("a", modifiers: [.command])修飾キーについて
修飾キーを1つ以上設定することができます。
capsLock
,shift
,control
,option
,command
,numericPad
, andfunction
.ユーザーは、アクションを実行するために、修飾キーとあなたが定義した文字キーを押す必要があります。
例えば、キーボードショートカットを
.keyboardShortcut("a", modifiers: [.command])
に設定した場合、ユーザーはコマンドキーと文字a
キーを同時に押す必要があります。キーボードショートカットを
.keyboardShortcut("r", modifiers: [.command, .option])
に設定した場合、ユーザーはコマンドキー、オプションキー、文字r
キーを同時に押す必要があります。キーボードのショートカットを追加できるUI要素
ボタンやスイッチ(トグル)にキーボードのショートカットを追加できます。
UIKitのアプリケーションに
UIKitを使ったアプリケーションの場合、
UIKeyCommand
オブジェクトを作成し、与えられた一連のキーボードショートカットでkeyCommands
プロパティをオーバーライドできます。class ViewController: UIViewController { override var keyCommands: [UIKeyCommand]? { return [ .init(title: "新しいメモを作成する", action: #selector(self.actionCreate), input: "n", modifierFlags: [.command]) ] } @objc func actionCreate() { print("create") } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } }タイトル、画像、実行するアクション、修飾フラグなどを指定できます。これがドキュメンテーションに記載されている関数のコードです。
public convenience init(title: String = "", image: UIImage? = nil, action: Selector, input: String, modifierFlags: UIKeyModifierFlags = [], propertyList: Any? = nil, alternates: [UICommandAlternate] = [], discoverabilityTitle: String? = nil, attributes: UIMenuElement.Attributes = [], state: UIMenuElement.State = .off)
- 投稿日:2021-02-20T00:45:45+09:00
画面遷移時の値渡しのやり方【Swift 5】
はじめに
この記事では、異なるファイル間で(classを跨いで)値をやりとりする方法を紹介していきます。
また、付随するものとして、segueを使わずに画面遷移を行う方法も紹介していきます。環境
・macOS catalina
・Xcode version 12.2
・swift 5.3.1コードを用いた画面遷移
まずは遷移先の情報を取得します。
この際、""内にはStoryBoard IDを、as!以下は、遷移先のcocoa touch ファイルの名前を入力します。
なお、StoryBoard IDは写真の赤丸で囲われた部分から入力します。この時、Use StoryBoard IDに忘れずにチェックを入れるようにしてください。//遷移先の情報の取得 let nextView = storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
次に、遷移の方法を指定します。(任意)
上の行では、遷移先の画面をどう表示するかを指定することができます。ここでは、.coverVertical, crossDissolveなどの方法を指定することができます。
下の行では、どのようなアニメーションで遷移を行うのかを指定することができます。nextView.modalTransitionStyle = .crossDissolve //遷移の仕方の設定 nextView.modalPresentationStyle = .fullScreen //遷移先の表示方法の設定
最後に、このコードを実行すると遷移が完了します。self.present(nextView, animated: true, completion: nil) //遷移の実行
コードでの遷移を行うメリットとしては、segueを使うよりも、画面遷移の際のイベントを実装しやすいことが挙げられます。
したがって、遷移の際に何らかの処理を行う際には、上記のようにコードを用いた方法で行う方が良いです。最後に、もう一度全体のコードを載せておきます。
let nextView = storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController nextView.modalTransitionStyle = .crossDissolve nextView.modalPresentationStyle = .fullScreen self.present(nextView, animated: true, completion: nil)遷移先に値を渡す
ここでは、SecondViewController.swiftに、FirstViewController.swiftから値を渡す場合を考えます。
まずは、値の受け手である、SecondViewController.swiftに、以下のように記述します。
//SecondViewController.swift import UIKit class SecondViewController: UIViewController{ override func viewDidLoad() { super.viewDidLoad() } //FirstViewControllerから受け取る値をいれる変数の宣言 var reciever = "" }この際、reciverが、値を受け取る変数です。実際に使うときは、ここに、値を受け取る変数を自由にセットしてください。
続いては、FirstViewController.swiftの中に、SecondViewController.swiftに値を渡す処理を入力していきます。
//FirstViewController.swift import UIKit class FirstViewController: UIViewController{ override func viewDidLoad() { super.viewDidLoad() } //→ここから遷移のコードなので、実際は遷移したいタイミングで実行される場所に書きます (ex. IBActionの処理の中など) //遷移先を取得(上記参照) let nextView = storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! ViewController //""内には、SecondViewControllerのstoryBoard IDを入力します nextView.modalTransitionStyle = .crossDissolve nextView.modalPresentationStyle = .fullScreen //SecondViewController.swift内の変数recieverに文字列"Hello, world!"を代入 nextView.reciever = "Hello, world!" //遷移を実行 self.present(nextView, animated: true, completion: nil) }上記の手順で、異なるファイル間での値渡しは完了です。
注意事項
遷移先(SecondViewController)にIBActionやIBOutlet接続されている値(LabelやTextfieldなど)は遷移時に指定してしまうとエラーが出てしまいます。
そのような時は一旦遷移先(SecondViewController)に一時的に値を受け取る変数を適当に宣言して、そちらに値を渡して、遷移後にSecondViewControllerのViewDidLoadなどでIBActionやIBOutlet接続されている値に代入するようにしてください。自作アプリの宣伝
私たちは、自作のリマインダーアプリ「タスクリマインダー -TaskReminder 課題管理-」を公開しています。
ダウンロードはこちらから!
https://apple.co/3jJZ1Cyhttps://apple.co/3jJZ1Cy
- 投稿日:2021-02-20T00:45:45+09:00
画面遷移時の値渡しのやり方
はじめに
この記事では、異なるファイル間で(classを跨いで)値をやりとりする方法を紹介していきます。
また、付随するものとして、segueを使わずに画面遷移を行う方法も紹介していきます。環境
・macOS catalina
・Xcode version 12.2
・swift 5.3.1コードを用いた画面遷移
まずは遷移先の情報を取得します。
この際、""内にはStoryBoard IDを、as!以下は、遷移先のcocoa touch ファイルの名前を入力します。
なお、StoryBoard IDは写真の赤丸で囲われた部分から入力します。この時、Use StoryBoard IDに忘れずにチェックを入れるようにしてください。//遷移先の情報の取得 let nextView = storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
次に、遷移の方法を指定します。(任意)
上の行では、遷移先の画面をどう表示するかを指定することができます。ここでは、.coverVertical, crossDissolveなどの方法を指定することができます。
下の行では、どのようなアニメーションで遷移を行うのかを指定することができます。nextView.modalTransitionStyle = .crossDissolve //遷移の仕方の設定 nextView.modalPresentationStyle = .fullScreen //遷移先の表示方法の設定
最後に、このコードを実行すると遷移が完了します。self.present(nextView, animated: true, completion: nil) //遷移の実行
コードでの遷移を行うメリットとしては、segueを使うよりも、画面遷移の際のイベントを実装しやすいことが挙げられます。
したがって、遷移の際に何らかの処理を行う際には、上記のようにコードを用いた方法で行う方が良いです。最後に、もう一度全体のコードを載せておきます。
let nextView = storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController nextView.modalTransitionStyle = .crossDissolve nextView.modalPresentationStyle = .fullScreen self.present(nextView, animated: true, completion: nil)遷移先に値を渡す
ここでは、SecondViewController.swiftに、FirstViewController.swiftから値を渡す場合を考えます。
まずは、値の受け手である、SecondViewController.swiftに、以下のように記述します。
//SecondViewController.swift import UIKit class SecondViewController: UIViewController{ override func viewDidLoad() { super.viewDidLoad() } //FirstViewControllerから受け取る値をいれる変数の宣言 var reciever = "" }この際、reciverが、値を受け取る変数です。実際に使うときは、ここに、値を受け取る変数を自由にセットしてください。
続いては、FirstViewController.swiftの中に、SecondViewController.swiftに値を渡す処理を入力していきます。
//FirstViewController.swift import UIKit class FirstViewController: UIViewController{ override func viewDidLoad() { super.viewDidLoad() } //遷移先を取得(上記参照) let nextView = storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! ViewController //""内には、SecondViewControllerのstoryBoard IDを入力します nextView.modalTransitionStyle = .crossDissolve nextView.modalPresentationStyle = .fullScreen //SecondViewController.swift内の変数recieverに文字列"Hello, world!"を代入 nextView.reciever = "Hello, world!" //遷移を実行 self.present(nextView, animated: true, completion: nil) }上記の手順で、異なるファイル間での値渡しは完了です。
自作アプリの宣伝
私たちは、自作アプリ「タスクリマインダー -TaskReminder 課題管理-」を公開しています。
ダウンロードはこちらから!
https://apple.co/3jJZ1Cyhttps://apple.co/3jJZ1Cy
- 投稿日:2021-02-20T00:30:12+09:00
【Swift】API、JSON解析を使う
今回はこちら↓で作った
Swift5Bokete
アプリの復習
【iOS14対応】未経験者がiPhoneアプリ開発者になるための全て iOS Boot Camp検索欄にキーワードを入力すると、関連する画像が出てきます。
気に入った画像に適当にネタを入れて決定
ボタンを押し、シェアする
ボタンを押すとスクリーンショットが作られ、Twitter等に投稿することができます。このアプリでは
①ライブラリの導入方法
②APIの使い方
③JSONの使い方
を学びました。cocoapodsでライブラリを導入する
ライブラリに関しては動画内でもこちらのサイトを参照されていたので、このサイトを参考にするのが良いかと思います。
今回使用するライブラリは
pod 'SwiftyJSON' => JSONを使用するライブラリ pod 'Alamofire' => ネットワークを使用するライブラリ pod 'SDwebImage' => URLで引っ張ってきた画像を高速で使用することができるライブラリの3つです。
UIは以下の通り。
画像上に各パーツの名前(インスタンス名)を書いています。
ViewController
↑で導入したライブラリを使用するのと、デバイス内のアルバムを使用するためにViewController.swiftimport Alamofire import SwiftyJSON import SDWebImage import Photosを
class
の前に追記します。作成したスクリーンショットをアルバムに保存するためのプログラムを、
viewDidLoad
内に記述します。viewController.swiftPHPhotoLibrary.requestAuthorization { (status) in switch(status){ case .authorized: break case .denied: break case .noDetermined: break case .restricted: break } }APIについて
APIは
Application Program Interface
の略です。
簡単に言うと、とあるサービスがソフトウェアの一部を公開して、他のソフトウェアに機能を使えるようにできます(多分)
公開元が発行しているAPIキーを使い、発行元が公開している仕様書に従ってプログラムを記述することで、自分のソフトウェアに機能を導入することができます。今回使用するのがpixabeyと言う無償画像提供サイトのAPI。
このAPIを使用して行いたいのは、アプリ内のSearchTextField
に入力したキーワードで画像をpixabay
から引っ張り出してくることです。
流れとしては、https://pixabay.com/api/?key=[個人のAPIキー]&q=[検索ワード]
でpixabay
のサーバーにリクエストを送ると、json形式
でレスポンス(検索結果)が返ってくるので、このデータにjson解析
を行って画像を引っ張ってきます。例えば検索ワードを
yellow+flower
とした場合、pixabay
のレスポンスは以下のような形で返ってきます。
プログラムを書きます。
まず、画像のURLを取得するメソッドを作成します。ViewController.swiftfunc getImages(keyword:String) { //APIを使う let url = "https://pixabay.com/api/?key=[個人のAPIキー]&q=\(keyword)" //Alamofireを使ってHTTPリクエストを行う AF.request(url, method: get, parameters: nil, encoding: JsonEncoding.default).responseJSON{ (response) in //<= クロージャー switch response.result{ case .success: //<=サーバーからリクエストを正常に受け取れたかどうかで条件分岐 //JSON形式で返ってきたデータにJSON解析を行う let json:JSON = JSON(response.date as Any) //<=ここでデータを取得する //必要なデータを取り出し変数に格納する var imageString = json["hits"][self.count]["webformatURL"].string //<=hitsの配列内にあるwebformatURLをとってくる self.odaiImageView.sd_setImage(with: URL(string: imageString!), completed:nil) case .failure: print(error) } } }流れをざっくり説明すると...
検索欄に入力されたキーワードが、APIキーと共にパラメーターになってpixabay
に送信されます。
すると、上のようなデータがJSON形式
と言う形で返ってきます。
そのデータは辞書型で値が入っています。
その中の、hits
というキーが持っている配列の中に、検索ワードと一致する分だけ同じ数の配列が入っています。
各配列の中のwebformatURL
を、ViewController
のodaiImageView
に表示させたい、というわけです。そして
次のお題
ボタンをタップすると、次の画像が表示されるようにしたいので、nextOdai
という名前でアクションを作成します。ViewController.swift@IBAction func nextOdai(_ sender: Any) { count += 1 //<= 冒頭でvar count = 0と宣言しておく if searchTextField.text == ""{ getImage(keyword: "funny") } else { getImage(keyword: searchTextField.text!) } }
count
をインクリメントすることで、次のお題
ボタンを押すたびにJSON
内の配列を順番に参照することができます。但し、
次へ
ボタンを押しまくって、["hits"]内の配列の数よりcount
が多くなってしまうと、参照する値がなくなってしまい、エラーになってしまします。これを避けるために、getImages
メソッド内のlet imageString =
....string
の行の次に、以下のプログラムを追記します。ViewController.swiftif imageString == nil { imageString = json["hits"][0]["webformatURL"].string //↓これは画像を表示できるようにするために導入したSDWebImageのメソッドです self.odaiImageView.sd_setImage(with: URL(string: imageString!), completed: nil) } else { self.odaiImageView.sd_setImage(with: URL(string: imageString!), completed: nil) }次に、?ボタンを押した時の機能を書きます。
ViewController.swift@IBAction func searchAction(_ sender: Any) { self.count = 0 if searchTextField.text == "" { getImages(keyword: "funny") } else { getImages(keyword: searchTextField.text!) } }上との違いはというと
self.count = 0
ですが、searchAction
は検索して一番最初のデータを持ってくるので、count = 0
、すなわち["hits"][0]
を持ってくる必要があるのでこの記述が必要です。(多分)次に
決定
ボタンが押された時の機能を書きます。
決定
ボタンが押された時の流れとしては、ViewController
で選択した画像とコメントを次の画面に遷移して渡す必要があります。
あと、画面間で値を受け渡しするためのprepare for segue
も一緒に書きます。ViewController.swift@IBAction func done(_sender: Any) { performSegue(withIdentifier: "next", sender: nil) } override func prepare(for segue: UIStoryBoardSegue, sender: Any?) { let shareVC = segue.destination as? ShareViewController shareVC?.resultImage = odaiImageView.image! shareVC?.commentString = commentTextView.text }遷移先のプログラムを書きます。
ShareViewController.swiftvar resultImage = UIImage() var commentsString = String() var screenShotImage = UIImage()上の2つの値は
ViewController
ですでに値を受け取っているので、viewDidLoad
メソッド内でShareViewController.swiftresultImageView.image = resultImage commentLabel.text = commentStringと記述して遷移先の画面に値を反映します。
次にスクリーンショットの機能を書きます。
これは何も考えず、こういう風に書くんだな、と思っておくのが良さそうです。ShareViewController.swiftfunc takeScreenshot(){ let width = CGFloat(UIScreen.main.bounds.size.width) let height = CGFloat(UIScreen.main.bounds.size.height/1.3) let size = CGSize(width: width, height: height) UIGraphicsBeginImageContextWithOptions(size, false, 0.0) self.view.drawHierarchy(in: view.bounds, afterScreenUpdates: true) screenShotImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() }そして、
共有
ボタンを押したらスクリーンショットが発動して共有できるようにしたいので、↑のメソッドを利用して、ShareViewController.swift@IBAction func share(_ sender: Any) { //スクリーンショットを撮る takeScreenshot() let items = [screenShotImage] as [Any] //アクティビティビューに乗っけてシェアする let activityView = UIActivityViewController(activityItems: items, applicationActivities: nil) present(activityView, animated: true, completion: nil) }これで一通り完成です。
感想
Ruby on Rails
でもいくつかライブラリ使用しましたが、Swift
のもあるんですね(あほ)。
!
だったり?
だったりself
書いたりって細かい違いのところがまだ把握できてないので、今後の学習の中でつかんでいきたいと思います。