20200528のSwiftに関する記事は7件です。

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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカライズファイルを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: xml

3.コマンドを実行

$ JSONToString --json_path Strings.json

4.生成したファイルを各プロジェクトに追加する

"hoge" = "hoge";
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hoge">hoge</string>
</resources>

まとめ

  • githubで管理することで1元管理が可能になった
  • 運用を考えて、ツールを自作するのは有効である

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】選択上限のある複数選択可能なTableViewの実装+スクロールしてもチェックマークがズレないようにする

選択上限をつけた複数選択可能なTableViewの実装が調べても出てこなかったので、簡単に作ってみました。

今回実現したいTableView
  1. 複数選択可能
  2. 選択状態がスクロールによって他のCellにズレない
  3. 選択できる数に上限をつける

サンプルプログラム

1. 複数選択を可能にする

MultipleTableView.swift
// tableViewのisMultipleTouchEnabledをtrueにする
isMultipleTouchEnabled = true

これだけで複数選択が可能になる。

2. 選択状態がスクロールによって他のCellにズレない

Modelに選択状態を持って、その値を元にCellinitする。

MultipleTableView.swift
func 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.swift
var kind: KindModel? {
    didSet {
       accessoryType = kind?.isSelected ?? false ? .checkmark : .none
       initLabel()
    }
}

選択状態のsetは次の 選択できる数に上限をつける と合わせて紹介します。

3. 選択できる数に上限をつける

選択状態に上限をつけるのは、TableViewに選択数のカウンタをつけるのが1番楽かなと思います。
もっとかっこいい方法はないかと考えたのですが、特に思いつきませんでした。。

MultipleTableView.swift
var 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にも同じような投稿がありましたが、解決ならずだったので諦めました。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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以上のデータを保存してもクラッシュを起こしていないと報告しています。

https://stackoverflow.com/a/41615483

ですから、ユーザーのプリファレンスや文字列・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 タブで、プラスボタンをクリックします。

Screen Shot 2020-05-27 at 2.23.12 PM.png

そして、iCloud 機能を追加します

Screen Shot 2020-05-27 at 2.26.28 PM.png

Key-value storage をオンにします

Screen Shot 2020-05-27 at 2.26.54 PM.png

そして、すべてのUserDefaults.standardNSUbiquitousKeyValueStore()に置き換えるだけです:

let store = NSUbiquitousKeyValueStore()
store.set("Tokyo", forKey: "currentCity")
let currentCity = store.string(forKey: "currentCity")

資源

Appleの公式文書

Core Data

主な用途

Core Data には幅広い用途があります。私の個人的な開発では、Core Data を使ってユーザーの日記、ToDoアイテム、ブックマークを保存しています。

Core Data はユーザーの情報を既存の多くのアプリに保存するために広く使われています。

長所

  • 構造化データの保存(データベーススキームを設計する必要があります)
  • 数分以内にクラウド同期サポートを有効にできる

短所

  • データベーススキームを変更するたびに、新しいデータベースバージョンを作成する必要があります。そうしないとアプリがクラッシュしてしまいます。

使い方

新しいアプリ

新しいアプリを作成する場合は、プロジェクトの作成時に Use Core Data を選択できます。

Screen Shot 2020-05-27 at 2.35.06 PM.png

既存のアプリ

また、既存のアプリに Core Data を簡単に追加できます。

最初に、データモデルファイルを作成します。キーボードのcommand-Nキーを押して、Data Model を検索します。

Screen Shot 2020-05-27 at 2.37.09 PM.png

新しい 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 をクリックします。

Screen Shot 2020-05-27 at 2.42.46 PM.png

作成した新規エンティティ・アイテムに TodoItem という名前を付けます。

作成した新しいアイテムをダブルクリックし、名前を付けるだけです。この例では、 TodoItem という名前を付けます。

新しいプロパティを追加する

Properties セクションのプラスアイコンをクリックして、新しいプロパティを追加します。

Screen Shot 2020-05-27 at 2.44.30 PM.png

ここでは、To-Doアプリに次のプロパティが必要です。

プロパティ名 タイプ
todoTaskName String
personName String
taskDeadline Date

タイプを設定するには、プロパティ名の横にあるドロップダウンメニューをクリックします:

Screen Shot 2020-05-27 at 2.47.09 PM.png

このような感じになります。

Screen Shot 2020-05-27 at 2.48.28 PM.png

Entity クラスのコードファイルを生成
  1. 作成した新しいエンティティ項目を選択します。右側のパネルで最後のセクションのアイコンを選択します :

Screen Shot 2020-05-27 at 3.05.23 PM.png

  1. Codegen 設定を Manual/ None に変更

以前の設定:

Screen Shot 2020-05-27 at 3.08.57 PM.png

新しい設定:

Screen Shot 2020-05-27 at 3.10.38 PM.png

  1. クラス定義ファイルを生成

最上部のメニューで Editor をクリックし、Create NSManagedObject subclass... をクリックします

こうしたサブクラスを生成することで、TodoItem を新規オブジェクトの作成に直接使用できるようになります。

Screen Shot 2020-05-27 at 3.11.37 PM.png

ファイルナビゲーターに以下のファイルが表示されます :

Screen Shot 2020-05-27 at 3.13.31 PM.png

新規レコードを作成
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 モデルバージョン」を見つけて、作成した新しいモデルバージョンに切り替えます。

左側のパネルで、作成したバージョンに緑のチェックマークが付いていることを確認してください。

資源

Appleの公式文書

クラウドサポート付きCore Data

Core Dataでアプリ制作

KeyChain (キーホルダー)

主な用途

ユーザーの機密情報を保存する場合は、Key Chainに保存することをお勧めします。Keychainは、iOSがユーザーのウェブサイトのユーザー名とパスワードを保存する場所です。暗号化されており、ユーザーのパスコードまたはFace ID (Touch ID)でのみアクセスできます。

長所

  • セキュリティー
  • 保存された認証情報は、ユーザーのデバイス間で同期できます。

短所:

  • ユーザーはMac OS KeychainアプリでKeychainに保存したデータを見ることができます
  • 通常、開発者はkeychainに文字列を保存します。データを保存することはお勧めしません。

使い方

keychainにアクセスするのに必要なコードはいくつかありますが、私はkeychainに簡単にアクセスできるオープンソースのGithubリポジトリを見つけました。

https://github.com/evgenyneu/keychain-swift

資源

Appleの公式文書

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

資源

Appleの公式文書

CloudKitダッシュボードの使用

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】iOSアプリ開発入門~ 画面遷移編1~

はじめに

前回は条件分岐など少しプログラミング的な処理を入れてUIを更新しする方法ついて投稿しました。
SwiftとUIの接続編3:https://qiita.com/euJcIKfcqwnzDui/items/93f010b989a4d333f0b9

基本的なアプリには画面がいくつかあります。
今回は画面を追加し、アプリを作る上で必須となる「画面遷移」について説明します。

少しプログラミングの話も出てくるかと思うので一度こちらの記事に目を通しておくといいかもしれません。

それではプロジェクトを開きます。
前回までで処理やUIが少し増えたので今回は新しいプロジェクトを作って進めます。
もちろん前回までのプロジェクトを使って頂いても構いません。

画面を追加する

遷移先画面用のファイルを追加する

まず画面遷移するためには遷移先の画面が必要です。
画面を作るためにはSwiftファイルとStoryboardのファイルが必要になります。
遷移先の画面はSecondViewControllerという名前にしてファイルを作っていきましょう。

まずXcodeから追加先のフォルダを右クリックします。
メニューが表示されるので[New File...]を選択します。
スクリーンショット 2020-05-26 23.12.59.png
まずSwiftファイルを作ります。
表示されたポップアップから[Swift File]を選択し[Next]をクリックします。
スクリーンショット 2020-05-26 23.17.00.png
[Save As:]に作りたいファイル名を拡張子なしで入力し[Create]をクリックします。
スクリーンショット 2020-05-26 23.19.47.png
SecondViewController.swiftが作成されました。
同じ手順でStoryboardファイルも作成します。
スクリーンショット 2020-05-26 23.25.02.png
スクリーンショット 2020-05-26 23.26.05.png
SecondViewController.storyboardが作成されました。

これでファイルの準備は完了です。
スクリーンショット 2020-05-26 23.28.52.png

画面クラスを作成する

画面を表示するためにはまず画面用のクラスが必要です。
クラス名をSecondViewControllerとして以下のようにSecondViewController.swiftに作成します。
ちなみにファイル名とその中に定義するクラス名は必ずしも一致している必要はありません。
ですがファイルを見ればどのクラスが書かれたファイルなのかすぐわかるように一致させることをおすすめします。

SecondViewController.swift
import UIKit

class SecondViewController: UIViewController {

}

これで画面のクラスは作成できました。
このコードについて簡単に説明します。

まずclass SecondViewControllerは定義するクラス名を表します。
このあたりは別記事の説明を参照してください。

クラス名に:を付けてUIViewControllerと書かれています。
これは継承といい、オブジェクト指向の考え方の1つです。
継承とはあるクラスをベースとして新しいクラスを作成する方法です。
継承元の特性を引き継がせるようなイメージです。拡張しているといってもいいかもしれません。
この段階ではSecondViewControllerにはプロパティもメソッドも持っていないのでUIViewControllerとほぼ同じ特性を持つクラスです。

UIViewControllerは全ての画面のベースとなるクラスです。
表示する画面となるViewや画面遷移のメソッドなどを持っています。
プロジェクト作成時に自動生成されたViewControllerクラスを確認するとこれを継承していることがわかります。

ではUIViewControllerはどこで定義されたクラスなのかというとUIKitというフレームワークライブラリとも言ったりしますが、そこで定義されています。
ライブラリはある機能を実現するための処理をまとめたものです。
UIKitはUIを構成するためのクラスを提供してくれています。
UILabelUIButtonなど頭に「UI」がついているものは全てUIKitが提供しているクラスです。

ライブラリが提供するクラスや機能を使う場合はファイルの頭でimport ライブラリと宣言してあげる必要があります。
今回はUIKitを使うので頭でimport UIKitとしています。

これで遷移先のクラスは作成できました。
遷移するための処理やその他基本的な処理はUIViewControllerがやってくれるのでSecondViewControllerの中身はとりあえず空で問題ありません。

遷移先のStoryboardを作成する

次はStoryboardを編集します。
SecondViewController.storyboardを開いてください。

作成したばかりなので空の状態です。
まずは土台となるUIViewControllerを配置します。
UIViewControllerもボタンなどの配置と同じです。
[+]をクリックしUIViewControllerを適当な位置に配置してください。
検索から「ViewController」と打てば早く見つけられるかと思います。
スクリーンショット 2020-05-27 1.12.12.png

ViewControllerを選択した状態で[Attribute Inspector]を選択してください。
Inspector内に[Is Initial View Controller]にチェックをいれます。
するとViewControllerの左側に矢印が出ます。
スクリーンショット 2020-05-27 1.15.15.png
ViewControllerは[+]から何個でも配置することができます。
そのため複数配置さえていた場合、このファイルのどのViewControllerを最初に表示すればいいのかInterface Builderがわからなくなります。
[Is Initial View Controller]は「このファイルで最初に表示するViewControllerです」というフラグです。

次はViewControllerにSecondViewControllerクラスを設定してあげます。
ViewControllerを選択し、[Identity Inspector]をクリックします。
[Class]という項目があるのでSecondViewControllerを設定します。
スクリーンショット 2020-05-27 1.25.25.png
ViewControllerの場合[Class]にはUIViewControllerを継承した全てのクラスのうち1つを選択することができます。
ここに設定すると先程作成したSecondViewControllerとStoryboardの中のViewControllerがひも付きます。
【補足】ここに設定するSecondViewControllerはファイル名ではなくクラス名です。

真っ白な画面では遷移したとき少しわかりにくいかもしれないので適当なラベルを配置しておきましょう。
スクリーンショット 2020-05-27 1.33.20.png

これで遷移先の画面は完成です。

画面遷移する

それでは画面遷移させます。
iOSアプリの画面遷移の方式は何パターンかあります。
今回はその中で最も単純なモーダルという方式を使って画面遷移させます。
モーダルの詳細と他の方式は次回紹介します。

セグエを設定する

画面遷移の実装方法は2通りあります。
ソースコードから直接画面オブジェクトを生成する方法とsegueを使う方法です。
segueもUIKitで提供されている機能の1つで画面と画面を繋ぐオブジェクトです。
矢印のようなものですが実際に見た方が早いです。

今回はこのsegueを使って画面遷移させてみます。

Main.storyboardを開いてください。

まずはStoryboard ReferenceというUIを配置します。
いつも通り[+]ボタンをクリックし、Storyboard Referenceを探します。
これをViewControllerの外側に配置してください。
スクリーンショット 2020-05-27 1.50.37.png
配置するとViewControllerの外側とUIの一覧にStoryboard Referenceが現れます。
スクリーンショット 2020-05-27 1.54.27.png
補足ですが、Storyboard ReferenceはViewControllerの外側に配置されるのでViewControllerの構成には影響しません。
UIの一覧を見ると[View Controller Scene]と同じ階層にあることがわかります。

Storyboard Referenceはその名の通りStoryboardへの参照です。
先程別ファイルとして遷移先としてSecondViewController.storyboardというStoryboardファイルを作成しました。
Main.storyboardSecondViewController.storyboardは全くの別ファイルなのでMain.storyboardには遷移先の情報が必要となります。
その別ファイル情報を設定するためのUIがStoryboard Referenceです。

ではStoryboard ReferenceにSecondViewController.storyboardの情報を設定してあげます。
Storyboard Referenceを選択してください。
選択すると[Attributes Inspector]にStoryboardを設定できる項目があります。
ここにSecondViewController.storyboardを設定してあげましょう。
スクリーンショット 2020-05-27 2.06.39.png
【補足】ここで設定しているのはファイル情報です。そのため[Storyboard]の項目に設定しているのはクラス名ではなくファイル名です。

次はsegueでViewControllerとStoryboard Referenceを繋げてあげます。
ViewControllerの上にあるバーをクリックします。
すると3つのアイコンが表示されるのでその一番左のアイコンをクリックします。
これはViewControllerのオブジェクトを表すアイコンです。
controlキーを押しながらこのアイコンをクリックし、Storyboard Referenceまで青い線を引っ張っていってください。
スクリーンショット 2020-05-27 2.11.15.png
するとStoryboard Referenceの近くに以下のようなポップアップが表示されます。
[Present Modally]を選択します。
これはモーダルで表示するという設定になります。
スクリーンショット 2020-05-27 2.15.17.png
設定するとViewControllerとStoryboard Referenceが矢印で繋がれます。
この矢印がsegueです。
スクリーンショット 2020-05-27 2.19.32.png
segueを選択し、[Attributs Inspector]をクリックしてください。
[Identifier]という項目があるのでsegueのIDを設定します。
このIDはファイル内のsegueの中で一意なものであれば何でも構いません。
ですがわかりやすくするために遷移先の名前を設定するのが一般的です。
スクリーンショット 2020-05-27 2.22.45.png

これでsegueの設定ができ、遷移元のStoryboardの設定は完了です。

画面遷移する

ここまでが画面遷移をするための準備となります。

それでは画面遷移させてみましょう。
ボタンタップのイベントで画面を遷移させたいのでMain.storyboardにボタンを配置し、ViewControllerクラスにタップイベントを紐付けてください。

ViewController.swift
import 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自身です。
実際にはperformSegueUIViewControllerのメソッドですが、クラスを継承した場合継承元クラスのメソッドを自分自身のメソッドとして呼び出すことができます。

これでモーダルという方式の画面遷移をsegueで実現することができました。
モーダルは画面の上に画面が乗るような遷移方式です。

パラメータを受け渡す

performSegueの第二引数senderでパラメータを次の画面に渡せるといいました。
アプリを作っているとどうしても一画面では処理を完結できず、次の画面に情報を渡してあげないと処理できないようなことがあります。
そういった場面ではsenderにパラメータを設定し、次の画面で値を保持してあげます。

実際にやってみましょう。
今回の例では1画面目でテキストをキーボードから入力し、遷移先の画面で入力した文字を表示します。

キーボードから文字を入力するにはUITextFieldを使用します。
Main.storyboardに配置しViewController.swiftにアウトレット接続してください。
(以下の例ではその他に「入力してください」というラベルも配置しています。)
スクリーンショット 2020-05-27 23.30.47.png
スクリーンショット 2020-05-27 23.33.47.png

ViewController.swift
class 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にアウトレット接続します。
スクリーンショット 2020-05-27 23.41.24.png

SecondViewController.swift
class SecondViewController: UIViewController {
    /// 入力された文字列を表示するラベル
    @IBOutlet weak var inputtedTextLabel: UILabel!

}

さらにSecondViewController.swiftに入力された文字列を格納しておくinputtedTextというプロパティを宣言しておきます。

SecondViewController.swift
class 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に処理を追加します。

確認のためprepareprint()入れて実行してみましょう。

ViewController.swift
class 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.swift
class 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での処理から。
UITextFieldtextプロパティにはユーザがキーボードから入力した文字列が入っています。
UILabelと似たような感じですね。
ここで取得した値をperformSegueの第二引数に画面遷移のパラメータとして引き渡しています。

prepareでは実際に遷移先にパラメータを設定しています。
UIStoryboardSeguedestinationには遷移先の画面オブジェクトが設定されています。
asキャストと呼ばれる処理で型を変換しています。
定義を見るとわかりますがdestinationUIViewController型です。
今回はSecondViewControllerinputtedTextにパラメータを設定してあげたいのですが、UIViewControllerにはinputtedTextは定義されていません。
そのため一度キャストで型変換してあげる必要があります。

if let ~~はSwift特有の書き方なので詳細については別の機会に説明します。
簡単にいうとdestinationSecondViewControllerならsecondViewControllerという変数を宣言するというような処理です。

let paramater = sender as? String
これもキャストしています。
senderAny型ですがinputtedTextString型なので型が合いません。
そのため一度キャストした値をparamaterに格納しています。

secondViewController.inputtedText = paramaterはそのままで、inputtedTextparamaterをセットしています。

これでSecondViewControllerにパラメータを引き渡すことができました。
パラメータを引き渡した後はSecondViewControllerは自由にinputtedTextを使うことができます。

この後はSecondViewControllerviewDidLoadなどでUILabelの表示をinputtedTextで更新してあげると表示が変更されます。
以下のように実装してから実行すると入力したテキストの値が遷移先に表示されます。

SecondViewController.swift
class 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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)

スクリーンショット 2020-05-27 23.47.08.png

↓間隔と配置を指定

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)

スクリーンショット 2020-05-27 23.50.19.png

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}
を入れる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む