- 投稿日:2020-05-28T16:57:06+09:00
CleanArchitecture ファイルの自動生成(iOS・Swift)
CleanArchitectureではファイルを大量に生成するので、できれば手動ではなく自動で行いたい。そのための手段をまとめた。
Xcodeのテンプレート機能
メリット
Xcodeの機能として、テンプレートファイルを用意しておいておくと、ファイル生成時に選ぶことができる。
Xcodeの機能にすぎず、特別な設定は不要でプロジェクトの中身に何ら影響がない。テンプレートファイルは~/Library/Developer/Xcode/Templatesに置く。デメリット
テンプレートファイルは何処かから拾ってくるか自分で書く・編集する必要がある。自分は下記から拾った。
参考
https://medium.com/swift2go/installing-the-clean-swift-template-in-xcode-6b4367006827
Kuri
メリット
普通に使いやすい
デメリット
ymlファイル・テンプレートファイルをプロジェクトルートに置かないといけない(プロジェクトの構成に影響がある)
参考
https://github.com/bannzai/Kuri
https://dev.classmethod.jp/articles/making-progress-ios-dev-using-kuri/
https://dev.classmethod.jp/articles/introduction-of-kuri-generate-command-option/
- 投稿日:2020-05-28T13:28:02+09:00
ローカライズファイルをAndroidとiOSで共通管理する
はじめに
AndroidとiOSでサービスを展開している場合、同じ文言を使っている。
プラットフォームごとに管理することで文言の誤字が発生しやすく、コストも上がる。
ローカライズファイルを効率よく管理する方法を考えました前提条件
- AndroidとiOSで共通のリソースを扱う
- 差分を追いやすいこと
githubを使って管理
- オリジナルデータとなる文言とkeyはjsonにして管理
- githubを使うことで差分を追いやすくなる
- CIを使うことで自動化できる
ローカライズファイルを更新するツール
- JSONからローカライズファイルを生成するのを簡単にするためにツールを作成した
- t-osawa-009/JSONToString
使い方
1.オリジナルデータとなるJSONファイルを追加
[ { "key": "hoge", "key_android": "hoge", "key_ios": "hoge", "value_android": "hoge", "value_ios": "hoge" }, ]2.ローカライズファイルの生成を設定する
.JSONToString.yml
ファイルを追加outputs: - key: key value_key: value_ios output: Strings/Localizable.strings format: strings sort: asc - key: key value_key: value_android output: Strings/strings.xml format: xml3.コマンドを実行
$ JSONToString --json_path Strings.json4.生成したファイルを各プロジェクトに追加する
"hoge" = "hoge";<?xml version="1.0" encoding="utf-8"?> <resources> <string name="hoge">hoge</string> </resources>まとめ
- githubで管理することで1元管理が可能になった
- 運用を考えて、ツールを自作するのは有効である
参考リンク
- 投稿日:2020-05-28T11:25:17+09:00
【Swift】選択上限のある複数選択可能なTableViewの実装+スクロールしてもチェックマークがズレないようにする
選択上限をつけた複数選択可能なTableViewの実装が調べても出てこなかったので、簡単に作ってみました。
今回実現したいTableView
- 複数選択可能
- 選択状態がスクロールによって他のCellにズレない
- 選択できる数に上限をつける
1. 複数選択を可能にする
MultipleTableView.swift// tableViewのisMultipleTouchEnabledをtrueにする isMultipleTouchEnabled = trueこれだけで複数選択が可能になる。
2. 選択状態がスクロールによって他のCellにズレない
Model
に選択状態を持って、その値を元にCell
をinit
する。MultipleTableView.swiftfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell( withIdentifier: R.reuseIdentifier.multipleTableViewCell.identifier, for: indexPath) as! MultipleTableViewCell cell.selectionStyle = .none cell.kind = kinds[indexPath.row] return cell }MultipleTableViewCell.swiftvar kind: KindModel? { didSet { accessoryType = kind?.isSelected ?? false ? .checkmark : .none initLabel() } }選択状態の
set
は次の 選択できる数に上限をつける と合わせて紹介します。3. 選択できる数に上限をつける
選択状態に上限をつけるのは、
TableView
に選択数のカウンタをつけるのが1番楽かなと思います。
もっとかっこいい方法はないかと考えたのですが、特に思いつきませんでした。。MultipleTableView.swiftvar selectedIndex: [Int] = [] func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let row = selectedIndex.filter({ $0 == indexPath.row }).first { guard let removeIndex = selectedIndex.firstIndex(of: row) else { return } selectedIndex.remove(at: removeIndex) kinds[indexPath.row].isSelected.toggle() return } // 今回は上限を3にしました if selectedIndex.count > 2 { return } kinds[indexPath.row].isSelected.toggle() selectedIndex.append(indexPath.row) }これで上限の管理は完了です!
didDeselectRowAt
使いたかったけど、なぜか呼ばれなかった。
SOFにも同じような投稿がありましたが、解決ならずだったので諦めました。。
- 投稿日:2020-05-28T09:58:06+09:00
SceneDelegateのシングルトンオブジェクトを取得する
UIApplicationが接続されたSceneを保持しています。
外部ディスプレイへの接続などを行わない一般的なiOSアプリの場合は基本的にconnectedScenesは1つとなると思います。
以下のようなコードでオブジェクトを取得できます。UIApplication.shared.connectedScenes.first?.delegate as! SceneDelegateまた、以下のようなユーティリティを定義しておくと使用が楽です。
extension SceneDelegate { static var shared: SceneDelegate { UIApplication.shared.connectedScenes.first?.delegate as! SceneDelegate } }上記関数は以下のように使用ができ、簡単にUIViewControllerをSwiftUIから表示することができます。
let controller = UIViewController() SceneDelegate.shared.window?.rootViewController.present(controller, animated: true)
- 投稿日:2020-05-28T07:01:06+09:00
iOSにユーザーデータを保存する方法と、そのためのコードの書き方: UserDefaults、Core Data、Key Chain、CloudKit
データの保存はiOSアプリの持つ主要な機能です。たとえば、ユーザーが指定した色などの環境設定を保存したり、ウェブサイトのトークンをアプリに保存したり、ToDoリストのアプリを作ってタスクを保存したりすることができます。データをシステムに保存する方法はいくつもあります。
目次
User Defaults
Core Data
Keychain
iCloud CloudKitこの記事では、上記の方法それぞれについて、サンプルコードを使って解説します。
User Defaults
主な用途
わずか2〜3行のコードで、
String
,Int
,Bool
, やどんなData
オブジェクトも保存・取り出しができます。通常は、ユーザーがオンボーディングを終えたかどうかや、天気アプリでのユーザーのデフォルト都市等、ユーザーのプリファレンスを保存するのに使用されます。
長所
- わずか数行のコードで簡単に実装
- App Extensions 間でも簡単に共有
- クラウドにデータを保存でき、ユーザーの所有デバイス間で同期可能( 同じApple ID でサインイン)
短所
シンプルなデータの保存のみに使います。データベーススキームはありません。また Apple によると、
UserDefaults
ストレージが512 KBに達すると警告が発せられます。そして Apple のドキュメンテーションには、ストレージが1MBに達するとアプリが終了するとあります。Currently, there is only a size limit for data stored to local user defaults on tvOS, which posts a warning notification when user defaults storage reaches 512kB in size, and terminates apps when user defaults storage reaches 1MB in size.
https://developer.apple.com/documentation/foundation/userdefaults/1617187-sizelimitexceedednotificationしかし、Stack Overflow のユーザーの一人は、1MB以上のデータを保存してもクラッシュを起こしていないと報告しています。
ですから、ユーザーのプリファレンスや文字列・Int値をいくらか保存したい程度であれば心配することはありません。
使い方
//To store a value UserDefaults.standard.set("Tokyo", forKey: "currentCity") //To read a value let currentCity = UserDefaults.standard.string(forKey: "currentCity")(クラウド同期バージョン)の使い方
iCloudの機能を有効にする
プロジェクト設定の
Signing & Capabilities
タブで、プラスボタンをクリックします。そして、
iCloud
機能を追加します
Key-value storage
をオンにしますそして、すべての
UserDefaults.standard
をNSUbiquitousKeyValueStore()
に置き換えるだけです:let store = NSUbiquitousKeyValueStore() store.set("Tokyo", forKey: "currentCity") let currentCity = store.string(forKey: "currentCity")資源
Core Data
主な用途
Core Data
には幅広い用途があります。私の個人的な開発では、Core Data
を使ってユーザーの日記、ToDoアイテム、ブックマークを保存しています。
Core Data
はユーザーの情報を既存の多くのアプリに保存するために広く使われています。長所
- 構造化データの保存(データベーススキームを設計する必要があります)
- 数分以内にクラウド同期サポートを有効にできる
短所
- データベーススキームを変更するたびに、新しいデータベースバージョンを作成する必要があります。そうしないとアプリがクラッシュしてしまいます。
使い方
新しいアプリ
新しいアプリを作成する場合は、プロジェクトの作成時に
Use Core Data
を選択できます。既存のアプリ
また、既存のアプリに
Core Data
を簡単に追加できます。最初に、データモデルファイルを作成します。キーボードのcommand-Nキーを押して、
Data Model
を検索します。新しい
Data Model
にあなたのアプリの名前をつけてください。例えば、ここでは私のアプリの名前はCoreDataDemo
なので、私はCoreDataDemo.xcdatamodeld
を作りました。そして
AppDelegate.swift
ファイルに次の行を追加します。// MARK: - Core Data stack lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "CoreDataDemo") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { print(error.userInfo) } }) return container }()この行で、
CoreDataDemo
をあなたのアプリの名前に置き換えてください。let container = NSPersistentContainer(name: "CoreDataDemo")データベース構造の作成
To-Doアイテムごとに次のプロパティを格納するTo-Doアプリを作成するとします:
- To-Doタスク名
- タスクの担当者
- タスクの期限
1.
.xcdatamodeld
ファイルを開き、Add Entity
をクリックします。作成した新規エンティティ・アイテムに
TodoItem
という名前を付けます。作成した新しいアイテムをダブルクリックし、名前を付けるだけです。この例では、
TodoItem
という名前を付けます。新しいプロパティを追加する
Properties
セクションのプラスアイコンをクリックして、新しいプロパティを追加します。ここでは、To-Doアプリに次のプロパティが必要です。
プロパティ名 タイプ todoTaskName String personName String taskDeadline Date タイプを設定するには、プロパティ名の横にあるドロップダウンメニューをクリックします:
このような感じになります。
Entity クラスのコードファイルを生成
- 作成した新しいエンティティ項目を選択します。右側のパネルで最後のセクションのアイコンを選択します :
Codegen
設定をManual/ None
に変更以前の設定:
新しい設定:
- クラス定義ファイルを生成
最上部のメニューで
Editor
をクリックし、Create NSManagedObject subclass...
をクリックしますこうしたサブクラスを生成することで、
TodoItem
を新規オブジェクトの作成に直接使用できるようになります。ファイルナビゲーターに以下のファイルが表示されます :
新規レコードを作成
func addNewToDoItem(todoTaskName: String, personName: String, taskDeadline: Date) { let appDelegate = UIApplication.shared.delegate as! AppDelegate let context = appDelegate.persistentContainer.viewContext let newItem = TodoItem(context: context) newItem.todoTaskName = todoTaskName newItem.personName = personName newItem.taskDeadline = taskDeadline do { try context.save() } catch { print(error.localizedDescription) } }既存のレコードを取得
func getAllPlannerDays() -> [TodoItem] { let appDelegate = UIApplication.shared.delegate as! AppDelegate let context = appDelegate.persistentContainer.viewContext let savedPlaceFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "TodoItem") do { let result = try context.fetch(savedPlaceFetch) if let convertedResult = result as? [TodoItem] { return convertedResult } } catch { return [] } return [] }変更
func modifyObject(_ object: TodoItem, newDeadline: Date) { object.taskDeadline = newDeadline //保存する let appDelegate = UIApplication.shared.delegate as! AppDelegate let context = appDelegate.persistentContainer.viewContext do { try context.save() } catch { print(error.localizedDescription) } }削除
func deleteObject(_ object: NSManagedObject) { let appDelegate = UIApplication.shared.delegate as! AppDelegate let context = appDelegate.persistentContainer.viewContext context.delete(object) //保存する do { try context.save() } catch { print(error.localizedDescription) } }プログラムから
TodoItem
をご自身のエンティティ名に置き換えてください。特定のレコードをクエリ中
すべての
TodoItem
に対しpersonName
をネコノヒー
としてクエリしましょうfunc getToDoItem(withPerson: String) -> [TodoItem] { let appDelegate = UIApplication.shared.delegate as! AppDelegate let context = appDelegate.persistentContainer.viewContext let savedPlaceFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "TodoItem") savedPlaceFetch.predicate = NSPredicate(format: "personName = %@", withPerson) do { let result = try context.fetch(savedPlaceFetch) return result as? [TodoItem] } catch { return [] } return [] } let allTodo = getToDoItem(withPerson: "ネコノヒー")ユーザーのコアデータをクラウドに同期する
すでにここにガイドを書いています:
https://qiita.com/MaShunzhe/items/2018455cab0803d78442
新しいデータベースバージョンの作成
データベースの新しいバージョン(プロパティの追加、変更、削除、または新しいエンティティの作成)を作成するたびに、データベースの新しいバージョンを作成する必要があります。
データベースモデルファイルを選択した状態で、「Editor エディター」をクリックし、「Add Model Version モデルバージョンの追加」をクリックします。
次に、ファイルナビゲーターで、データベースモデルファイルの左側にある三角形のアイコンをクリックします。
作成した新しいデータモデルバージョンをクリックします。
右側のパネルでファイルアイコンをクリックし、「Model Version モデルバージョン」を見つけて、作成した新しいモデルバージョンに切り替えます。
左側のパネルで、作成したバージョンに緑のチェックマークが付いていることを確認してください。
資源
KeyChain (キーホルダー)
主な用途
ユーザーの機密情報を保存する場合は、Key Chainに保存することをお勧めします。Keychainは、iOSがユーザーのウェブサイトのユーザー名とパスワードを保存する場所です。暗号化されており、ユーザーのパスコードまたはFace ID (Touch ID)でのみアクセスできます。
長所
- セキュリティー
- 保存された認証情報は、ユーザーのデバイス間で同期できます。
短所:
- ユーザーはMac OS KeychainアプリでKeychainに保存したデータを見ることができます
- 通常、開発者はkeychainに文字列を保存します。データを保存することはお勧めしません。
使い方
keychainにアクセスするのに必要なコードはいくつかありますが、私はkeychainに簡単にアクセスできるオープンソースのGithubリポジトリを見つけました。
https://github.com/evgenyneu/keychain-swift
資源
CloudKit
長所
- - データをプライベート・データベース、共有データベース、またはパブリック・データベース(すべてのユーザーが他のユーザーのレコードをフェッチできる)に保存できます。
- CloudKitはAppleの無料クラウドサービスです。
- CloudKitプライベート・データベースへの保存は、ユーザー自身のiCloudストレージスペースに対してカウントされます。
- CloudKitパブリック・データベースへの保存は、割り当て制限に対してカウントされます。
- ユーザーがアプリを削除してから再度インストールすると、CloudKitに保存されているデータをフェッチできます。
短所
- ユーザーはオンラインである必要があります。また、CloudKitデータベースの使用にはサイズ制限があります。
https://developer.apple.com/icloud/cloudkit/
実装
私は以前、「CloudKit」を使用してユーザーの情報を保存および取得する方法についてのガイドを紹介しました:
https://qiita.com/MaShunzhe/items/bc21527259c5da7f66ed
https://qiita.com/MaShunzhe/items/9291c86a3d0442ddf759
資源
- 投稿日:2020-05-28T01:15:11+09:00
【iOS】iOSアプリ開発入門~ 画面遷移編1~
はじめに
前回は条件分岐など少しプログラミング的な処理を入れてUIを更新しする方法ついて投稿しました。
SwiftとUIの接続編3:https://qiita.com/euJcIKfcqwnzDui/items/93f010b989a4d333f0b9基本的なアプリには画面がいくつかあります。
今回は画面を追加し、アプリを作る上で必須となる「画面遷移」について説明します。少しプログラミングの話も出てくるかと思うので一度こちらの記事に目を通しておくといいかもしれません。
それではプロジェクトを開きます。
前回までで処理やUIが少し増えたので今回は新しいプロジェクトを作って進めます。
もちろん前回までのプロジェクトを使って頂いても構いません。画面を追加する
遷移先画面用のファイルを追加する
まず画面遷移するためには遷移先の画面が必要です。
画面を作るためにはSwiftファイルとStoryboardのファイルが必要になります。
遷移先の画面はSecondViewController
という名前にしてファイルを作っていきましょう。まずXcodeから追加先のフォルダを右クリックします。
メニューが表示されるので[New File...]を選択します。
まずSwiftファイルを作ります。
表示されたポップアップから[Swift File]を選択し[Next]をクリックします。
[Save As:]に作りたいファイル名を拡張子なしで入力し[Create]をクリックします。
SecondViewController.swift
が作成されました。
同じ手順でStoryboardファイルも作成します。
SecondViewController.storyboard
が作成されました。画面クラスを作成する
画面を表示するためにはまず画面用のクラスが必要です。
クラス名をSecondViewController
として以下のようにSecondViewController.swift
に作成します。
ちなみにファイル名とその中に定義するクラス名は必ずしも一致している必要はありません。
ですがファイルを見ればどのクラスが書かれたファイルなのかすぐわかるように一致させることをおすすめします。SecondViewController.swiftimport UIKit class SecondViewController: UIViewController { }これで画面のクラスは作成できました。
このコードについて簡単に説明します。まず
class SecondViewController
は定義するクラス名を表します。
このあたりは別記事の説明を参照してください。クラス名に
:
を付けてUIViewController
と書かれています。
これは継承といい、オブジェクト指向の考え方の1つです。
継承とはあるクラスをベースとして新しいクラスを作成する方法です。
継承元の特性を引き継がせるようなイメージです。拡張しているといってもいいかもしれません。
この段階ではSecondViewController
にはプロパティもメソッドも持っていないのでUIViewController
とほぼ同じ特性を持つクラスです。
UIViewController
は全ての画面のベースとなるクラスです。
表示する画面となるViewや画面遷移のメソッドなどを持っています。
プロジェクト作成時に自動生成されたViewController
クラスを確認するとこれを継承していることがわかります。では
UIViewController
はどこで定義されたクラスなのかというとUIKit
というフレームワーク、ライブラリとも言ったりしますが、そこで定義されています。
ライブラリはある機能を実現するための処理をまとめたものです。
UIKit
はUIを構成するためのクラスを提供してくれています。
UILabel
やUIButton
など頭に「UI」がついているものは全てUIKitが提供しているクラスです。ライブラリが提供するクラスや機能を使う場合はファイルの頭で
import ライブラリ
と宣言してあげる必要があります。
今回はUIKit
を使うので頭でimport UIKit
としています。これで遷移先のクラスは作成できました。
遷移するための処理やその他基本的な処理はUIViewController
がやってくれるのでSecondViewController
の中身はとりあえず空で問題ありません。遷移先のStoryboardを作成する
次はStoryboardを編集します。
SecondViewController.storyboard
を開いてください。作成したばかりなので空の状態です。
まずは土台となるUIViewController
を配置します。
UIViewController
もボタンなどの配置と同じです。
[+]をクリックしUIViewController
を適当な位置に配置してください。
検索から「ViewController」と打てば早く見つけられるかと思います。
ViewControllerを選択した状態で[Attribute Inspector]を選択してください。
Inspector内に[Is Initial View Controller]にチェックをいれます。
するとViewControllerの左側に矢印が出ます。
ViewControllerは[+]から何個でも配置することができます。
そのため複数配置さえていた場合、このファイルのどのViewControllerを最初に表示すればいいのかInterface Builderがわからなくなります。
[Is Initial View Controller]は「このファイルで最初に表示するViewControllerです」というフラグです。次はViewControllerに
SecondViewController
クラスを設定してあげます。
ViewControllerを選択し、[Identity Inspector]をクリックします。
[Class]という項目があるのでSecondViewController
を設定します。
ViewControllerの場合[Class]にはUIViewController
を継承した全てのクラスのうち1つを選択することができます。
ここに設定すると先程作成したSecondViewController
とStoryboardの中のViewControllerがひも付きます。
【補足】ここに設定するSecondViewController
はファイル名ではなくクラス名です。真っ白な画面では遷移したとき少しわかりにくいかもしれないので適当なラベルを配置しておきましょう。
これで遷移先の画面は完成です。
画面遷移する
それでは画面遷移させます。
iOSアプリの画面遷移の方式は何パターンかあります。
今回はその中で最も単純なモーダルという方式を使って画面遷移させます。
モーダルの詳細と他の方式は次回紹介します。セグエを設定する
画面遷移の実装方法は2通りあります。
ソースコードから直接画面オブジェクトを生成する方法とsegueを使う方法です。
segueもUIKit
で提供されている機能の1つで画面と画面を繋ぐオブジェクトです。
矢印のようなものですが実際に見た方が早いです。今回はこのsegueを使って画面遷移させてみます。
Main.storyboard
を開いてください。まずはStoryboard ReferenceというUIを配置します。
いつも通り[+]ボタンをクリックし、Storyboard Referenceを探します。
これをViewControllerの外側に配置してください。
配置するとViewControllerの外側とUIの一覧にStoryboard Referenceが現れます。
補足ですが、Storyboard ReferenceはViewControllerの外側に配置されるのでViewControllerの構成には影響しません。
UIの一覧を見ると[View Controller Scene]と同じ階層にあることがわかります。Storyboard Referenceはその名の通りStoryboardへの参照です。
先程別ファイルとして遷移先としてSecondViewController.storyboard
というStoryboardファイルを作成しました。
Main.storyboard
とSecondViewController.storyboard
は全くの別ファイルなのでMain.storyboard
には遷移先の情報が必要となります。
その別ファイル情報を設定するためのUIがStoryboard Referenceです。ではStoryboard Referenceに
SecondViewController.storyboard
の情報を設定してあげます。
Storyboard Referenceを選択してください。
選択すると[Attributes Inspector]にStoryboardを設定できる項目があります。
ここにSecondViewController.storyboard
を設定してあげましょう。
【補足】ここで設定しているのはファイル情報です。そのため[Storyboard]の項目に設定しているのはクラス名ではなくファイル名です。次はsegueでViewControllerとStoryboard Referenceを繋げてあげます。
ViewControllerの上にあるバーをクリックします。
すると3つのアイコンが表示されるのでその一番左のアイコンをクリックします。
これはViewControllerのオブジェクトを表すアイコンです。
controlキーを押しながらこのアイコンをクリックし、Storyboard Referenceまで青い線を引っ張っていってください。
するとStoryboard Referenceの近くに以下のようなポップアップが表示されます。
[Present Modally]を選択します。
これはモーダルで表示するという設定になります。
設定するとViewControllerとStoryboard Referenceが矢印で繋がれます。
この矢印がsegueです。
segueを選択し、[Attributs Inspector]をクリックしてください。
[Identifier]という項目があるのでsegueのIDを設定します。
このIDはファイル内のsegueの中で一意なものであれば何でも構いません。
ですがわかりやすくするために遷移先の名前を設定するのが一般的です。
これでsegueの設定ができ、遷移元のStoryboardの設定は完了です。
画面遷移する
ここまでが画面遷移をするための準備となります。
それでは画面遷移させてみましょう。
ボタンタップのイベントで画面を遷移させたいのでMain.storyboard
にボタンを配置し、ViewController
クラスにタップイベントを紐付けてください。ViewController.swiftimport UIKit class ViewController: UIViewController { /// 画面遷移ボタンタップ処理 /// - Parameter sender: ボタン @IBAction func didTapTransitionButton(_ sender: Any) { } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } }処理自体は非常に簡単で
performSegue
というメソッドを使用するだけです。
以下を実装して実行するとSecondViewController
画面に遷移します。ViewController.swift@IBAction func didTapTransitionButton(_ sender: Any) { self.performSegue(withIdentifier: "SecondViewController", sender: nil) }
performSegue
というメソッドはUIViewController
で定義されているメソッドで、segueを使って画面遷移するためのメソッドです。
第一引数のwithIdentifier
は先程Storyboardでsegueに設定したIDです。
設定したID以外のものを設定するとアプリがクラッシュするので気をつけてください。
第二引数のsender
は画面遷移の際、次の画面に受け渡したいパラメータを設定します。
今回nil
を設定していますがこれは何も設定しないという意味です。他の言語ではnull
と呼ばれています。メソッドの先頭に
self
というものを付けていますがこれは自分自身のオブジェクトという意味です。
今回でいうとViewController
自身です。
実際にはperformSegue
はUIViewController
のメソッドですが、クラスを継承した場合継承元クラスのメソッドを自分自身のメソッドとして呼び出すことができます。これで
モーダル
という方式の画面遷移をsegueで実現することができました。
モーダルは画面の上に画面が乗るような遷移方式です。パラメータを受け渡す
performSegue
の第二引数sender
でパラメータを次の画面に渡せるといいました。
アプリを作っているとどうしても一画面では処理を完結できず、次の画面に情報を渡してあげないと処理できないようなことがあります。
そういった場面ではsender
にパラメータを設定し、次の画面で値を保持してあげます。実際にやってみましょう。
今回の例では1画面目でテキストをキーボードから入力し、遷移先の画面で入力した文字を表示します。キーボードから文字を入力するにはUITextFieldを使用します。
Main.storyboard
に配置しViewController.swift
にアウトレット接続してください。
(以下の例ではその他に「入力してください」というラベルも配置しています。)
ViewController.swiftclass ViewController: UIViewController { /// テキストフィールド @IBOutlet weak var textField: UITextField! /// 画面遷移ボタンタップ処理 /// - Parameter sender: ボタン @IBAction func didTapTransitionButton(_ sender: Any) { self.performSegue(withIdentifier: "SecondViewController", sender: nil) } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } }次に遷移先の
SecondViewController
に渡された文字列を表示するためのUILabelを配置してください。
配置したUILabelをSecondViewController.swift
にアウトレット接続します。
SecondViewController.swiftclass SecondViewController: UIViewController { /// 入力された文字列を表示するラベル @IBOutlet weak var inputtedTextLabel: UILabel! }さらに
SecondViewController.swift
に入力された文字列を格納しておくinputtedText
というプロパティを宣言しておきます。SecondViewController.swiftclass SecondViewController: UIViewController { /// 入力された文字列を表示するラベル @IBOutlet weak var inputtedTextLabel: UILabel! /// 入力された文字列を格納しておくプロパティ var inputtedText: String? }
inputtedText
の型がString?
となっていますが?
はオプショナル型を表します。
オプショナル型はSwift特有の考え方なのですが、値がnilの可能性があるという意味でString?
とするとnilになり得るString
型という意味になります。
UITextField
のテキストの初期値はnilでSecondViewController
に引き渡される文字列がnilになる可能性があるためオプショナル型で宣言しています。
今回はそういうものがあるのかくらいの認識で問題ありません。さてこれで準備が完了しました。
実際にパラメータを引き渡すコードを書いていきましょう。パラメータを引き渡すにはまず画面遷移のイベントをフックしてあげる必要があります。
イベントをフックするとは、イベントが発生したときに処理を割り込ませるような仕組みになります。
例えばボタンタップのメソッドもイベントが発生したときに処理を割り込ませているのでフックの一種になるかと思います。
UIViewController
にはsegueでの画面遷移イベントをフックするメソッドが用意されています。
prepare(for segue: UIStoryboardSegue, sender: Any?)
というメソッドです。
これを利用してパラメータを引き渡してあげます。画面遷移を実行するのは遷移元である
ViewController
の処理なのでViewController.swift
に処理を追加します。確認のため
prepare
にprint()
入れて実行してみましょう。ViewController.swiftclass ViewController: UIViewController { /// テキストフィールド @IBOutlet weak var textField: UITextField! /// 画面遷移ボタンタップ処理 /// - Parameter sender: ボタン @IBAction func didTapTransitionButton(_ sender: Any) { self.performSegue(withIdentifier: "SecondViewController", sender: nil) } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } /// 画面遷移イベントをフックする /// - Parameters: /// - segue: segue /// - sender: パラメータ override func prepare(for segue: UIStoryboardSegue, sender: Any?) { print("画面遷移前の処理!") } }
func prepare
の前にoverride
とあります。
これは継承元となったクラスに定義されているメソッドを上書きするという意味になります。
UIViewController
ではperformSegue
を呼んだ後、segueに紐付けられた画面オブジェクトを作成し、prepare
を呼び出すというような処理を行っています。
ここで呼ばれるprepare
の処理を上書いてあげることで画面遷移前に好きな処理を記述できるようになります。
prepare
の引数について。
第一引数はsegueには画面遷移に使用したsegueが渡されてきます。
今回の場合だとSecondViewController
へのsegueです。
segueの中には遷移先の情報があり、遷移先の画面オブジェクトも含まれます。
このsegueから取り出した画面オブジェクトに対し好きな処理をすることができます。第二引数の
sender
にはperformSegue
の第二引数に設定した値が入ります。それでは実際にパラメータを引き渡す処理を実装します。
以下のように実装してください。ViewController.swiftclass ViewController: UIViewController { /// テキストフィールド @IBOutlet weak var textField: UITextField! /// 画面遷移ボタンタップ処理 /// - Parameter sender: ボタン @IBAction func didTapTransitionButton(_ sender: Any) { // textFieldから入力されているテキストを取得する let text = textField.text // 画面遷移のパラメータとしてtextを設定する self.performSegue(withIdentifier: "SecondViewController", sender: text) } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } /// 画面遷移イベントをフックする /// - Parameters: /// - segue: segue /// - sender: パラメータ override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // segueからSecondViewControllerを取得する if let secondViewController = segue.destination as? SecondViewController { // senderをStringにキャストする let paramater = sender as? String // SecondViewControllerのinputtedTextにparamaterを設定する secondViewController.inputtedText = paramater } } }まずは
didTapTransitionButton
での処理から。
UITextField
のtext
プロパティにはユーザがキーボードから入力した文字列が入っています。
UILabel
と似たような感じですね。
ここで取得した値をperformSegue
の第二引数に画面遷移のパラメータとして引き渡しています。
prepare
では実際に遷移先にパラメータを設定しています。
UIStoryboardSegue
のdestination
には遷移先の画面オブジェクトが設定されています。
as
はキャストと呼ばれる処理で型を変換しています。
定義を見るとわかりますがdestination
はUIViewController
型です。
今回はSecondViewController
のinputtedText
にパラメータを設定してあげたいのですが、UIViewController
にはinputtedText
は定義されていません。
そのため一度キャストで型変換してあげる必要があります。
if let ~~
はSwift特有の書き方なので詳細については別の機会に説明します。
簡単にいうとdestination
がSecondViewController
ならsecondViewController
という変数を宣言するというような処理です。
let paramater = sender as? String
これもキャストしています。
sender
はAny
型ですがinputtedText
はString
型なので型が合いません。
そのため一度キャストした値をparamater
に格納しています。
secondViewController.inputtedText = paramater
はそのままで、inputtedText
にparamater
をセットしています。これで
SecondViewController
にパラメータを引き渡すことができました。
パラメータを引き渡した後はSecondViewController
は自由にinputtedText
を使うことができます。この後は
SecondViewController
のviewDidLoad
などでUILabel
の表示をinputtedText
で更新してあげると表示が変更されます。
以下のように実装してから実行すると入力したテキストの値が遷移先に表示されます。SecondViewController.swiftclass SecondViewController: UIViewController { /// 入力された文字列を表示するラベル @IBOutlet weak var inputtedTextLabel: UILabel! /// 入力された文字列を格納しておくプロパティ var inputtedText: String? /// 画面読み込み完了後処理 override func viewDidLoad() { super.viewDidLoad() // inputtedTextLabelの表示をinputtedTextの値にする self.inputtedTextLabel.text = self.inputtedText } }もしキーボードで画面遷移ボタンが押せない場合は
ViewController.swift
に以下の処理を追加してください。
画面のどこかをタップするとキーボードを閉じるという処理です。ViewController.swift/// 画面タップでテキストフィールドを閉じる /// - Parameters: /// - touches: touches /// - event: event override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { self.view.endEditing(true) }最後に
今回は画面遷移の方法とパラメータの引き渡し方について説明しました。
だんだんとSwiftプログラミングの要素が入ってきたので難しくなってきたかと思います。
ですが画面遷移はアプリで必ずといっていいほど入っている処理なので習得してください。今回モーダル遷移を取り扱ったので次回は別の方式について説明します。
画面遷移編2:https://qiita.com/euJcIKfcqwnzDui/items/6d37aaf00c0bc7ce26ca本連載ではプログラミング未経験からiOSアプリ開発が行えるようになることを目的としています。
今までの投稿をまとめていますのでこちらもご覧ください。
アジェンダ:https://qiita.com/euJcIKfcqwnzDui/items/0b480e96166e88945684
- 投稿日:2020-05-28T00:22:56+09:00
SwiftUIのレイアウト
一度に複数のビューを画面に表示する場合は子ビューをまとめるビューが必要。
VStackとHStack
Vstackは縦並び
HStackは横並びVStack{ Text("hoge") Text("hogehoge") }従来のSwiftの構文から考えれば、インスタンスを作成し使っていない、代入もリターンも行っていない
クロージャ引数に@ViewBuilderを付与することでクロージャ内の各行が引数となりViewBuilderのbuildBlockメソッドに渡されるつまり
Vstack{ ViewBuilder.buildBlock(Text("hoge"), Text("hogehoge")) }を省略した文章。
buildBockの中には2~10個のビューを受け渡せる。基本的な使い方
VStack{ VStack{ Text("Hello World!") Text("SwiftUI!") }.background(Color.red) HStack{ Text("Hello World!") Text("SwiftUI!").font(.largeTitle) }.background(Color.blue) Text("SwiftUI") .background(Color.green) } .frame(width:250,height:150) .background(Color.yellow)↓間隔と配置を指定
VStack(alignment: .leading,spacing: 20){ VStack{ Text("Hello World!") Text("SwiftUI!") }.background(Color.red) HStack(alignment: .bottom){ Text("Hello World!") Text("SwiftUI!").font(.largeTitle) }.background(Color.blue) Text("SwiftUI") .background(Color.green) } .frame(width:250,height:150) .background(Color.yellow)Spacerビュー
Spacingで要素と要素の間隔を固定値ではなく伸縮させたい時につかう
HStack{ Spacer() Text("Hello World!") Spacer() Text("SwiftUI!") Spacer() }Dividerビュー
仕切り線を引く多分htmlのhrと同じ
VStack{ Text("Hello World!") Divider() Text("Hello World!") }ZStack
写真の上に文字を載せたり、要素と要素を重ねるときに使う
overlayモディファイアと似ているが、overlayはビューサイズの制限、重ねる枚数が増えればコードが複雑になることからZStackのほうがよく使用されるZStack(alignment: .top){ Image("back_image") Text("hoge") .font(.largeTitle) .foregroundColor(.white) .padding(.top) }ビュー構築にif文を使う
if Bool.random(){ return Text("true") }else{ return Text("false") }Bool.random()は実行するたびにランダムでtrueかfalseを返す
この時どちらかがテキスト、どちらかが画像の場合、実行しないと型が定まらないのでエラーになる。
そういった時はGrouopビューを使う。Groupビュー
戻り値がGroupビューになるのでエラーが発生しない
groupでなくてもVStackやHStackのように複数のビューをまとめることができれば問題ないGroup{ if Bool.random(){ Image("true_image") }else{ Text("false") } }ビューのアライメントの調整
HStackで横並びした要素の一番下をベースラインで揃えたい時に使用
HStack(alignment: .lastTextBaseline){ Text("hogehoge") Image("hoge_image") Text("hogehoge") }一部の要素の水平位置を変更したい時はalignmentGuideモディファイアをつかう
HStack(alignment: .lastTextBaseline){ Text("hogehoge") Image("hoge_image") .alignmentGuide(.lastTextBaseline){d in d.height * 0.87}//13%は下に突き出ている Text("hogehoge") }横に突き出したい場合も同様
VStack(alignment: .leading){ Text("hogehoge") Image("hoge_image") .alignmentGuide(.leading){d in d[.leading] + 10}//10px左に突き出ている Text("hogehoge") }カスタムアライメント
横並びの要素の上下でずれた部分を揃える時は
HStackの引数に(alignment: .customAlign)
揃えたい要素それぞれに
.alignmentGuide(.customAlign){d in d.height/2}
を入れる