- 投稿日: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-20T21:29:43+09:00
【AVFoundation】Apple ProRAW/RAWイメージを撮影する
iOS 14.3以降を搭載したiPhone 12 Pro、iPhone 12 Pro MaxでApple ProRAWを撮影することができるようになりました。AVCaptureSession周りを構築して通常の写真撮影を行う方法に関する記事は既にたくさんありますので、ここではApple ProRAW/RAWを撮影するための設定と撮影/保存のハンドリング部分に絞って書きます。
AVCapturePhotoOutputの設定
Apple ProRAWを撮影するためにはAVCaptureSessionに追加したAVCapturePhotoOutputのisAppleProRAWEnabledを有効にする必要があります。Apple ProRAWではなく、通常のRAWイメージを撮影する時はこちらの項目の処理は不要です。
また、iPhone 12 Pro/iPhone 12 Pro Max以外の機種だと、isAppleProRAWSupportedはfalseになります。photoOutput.isAppleProRAWEnabled = photoOutput.isAppleProRAWSupportedAVCapturePhotoSettingsの設定
Apple ProRAW/RAWイメージを撮影してフォトライブラリに保存する場合は、「Apple ProRAW/RAWイメージ」 + 「加工済みイメージ」を一つのアセットとして保存する必要があります(以下の例だと「Apple ProRAW/RAWイメージ」 +「JPEGイメージ」)。そのため、AVCapturePhotoSettingsに「Apple ProRAW/RAWイメージ」と「加工済みイメージ」のフォーマットをそれぞれ指定する必要があります。
let query = photoOutput.isAppleProRAWEnabled ? { AVCapturePhotoOutput.isAppleProRAWPixelFormat($0) } : { AVCapturePhotoOutput.isBayerRAWPixelFormat($0) } // Apple ProRAW/RAWイメージのフォーマットを指定 guard let rawFormat = photoOutput.availableRawPhotoPixelFormatTypes.first(where: query) else { fatalError("No RAW format found.") } // 加工済みイメージのフォーマットを指定 let processedFormat = [AVVideoCodecKey: AVVideoCodecType.jpeg] // 撮影するイメージのフォーマットをAVCapturePhotoSettingsに指定 let photoSettings = AVCapturePhotoSettings(rawPixelFormatType: rawFormat, processedFormat: processedFormat) photoOutput.capturePhoto(with: photoSettings, delegate: self)Apple ProRAW/RAWイメージの撮影
撮影を開始すると以下のデリゲートメソッドが2回コールされます。一度目のコールではApple ProRAW/RAWイメージが配信されるタイミング、2回目は加工済みイメージが配信されるタイミングでコールされます。ここではApple ProRAW/RAWイメージを一時的にtmpディレクトリに保存しておき、加工済みイメージを変数で保持しています。
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { // 配信されるイメージデータの種類を判定する if photo.isRawPhoto { self.rawImageFileURL = self.makeUniqueTempFileURL(extension: "dng") do { // Apple ProRAW/RAWイメージをtmpディレクトリに保存 try photo.fileDataRepresentation()!.write(to: rawImageFileURL!) } catch { fatalError("couldn't write DNG file to URL") } } else { // 加工済みイメージを変数で保持 self.compressedData = photoData } } /** @brief Apple ProRAW/イメージファイルの保存先URLを生成する */ private func makeUniqueDNGFileURL() -> URL { let tempDir = FileManager.default.temporaryDirectory let fileName = ProcessInfo.processInfo.globallyUniqueString return tempDir.appendingPathComponent(fileName).appendingPathExtension("dng") }Apple ProRAW/RAWイメージの保存
撮影が完了したタイミングで以下のデリゲートメソッドがコールされます。「AVCapturePhotoSettingsの設定」で記載したように、Apple ProRAW/RAWイメージを撮影してフォトライブラリに保存する場合は「Apple ProRAW/RAWイメージ」 + 「加工済みイメージ」を一つのアセットとして保存する必要があります。加工済みイメージをメインイメージリソース、Apple ProRAW/RAWイメージを代替イメージリソースとしてアセットを保存するとApple ProRAW/RAWイメージの保存が完了します。
func photoOutput(_ output: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) { PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in guard status == .authorized else { return } PHPhotoLibrary.shared().performChanges { // 加工済みイメージをメインイメージリソースとして保存 let creationRequest = PHAssetCreationRequest.forAsset() creationRequest.addResource(with: .photo, data: compressedData, options: nil) // Apple ProRAW/RAWイメージを代替イメージリソースとして保存 let options = PHAssetResourceCreationOptions() options.shouldMoveFile = true creationRequest.addResource(with: .alternatePhoto, fileURL: rawFileURL, options: options) } completionHandler: { success, error in } } }写真アプリを起動して撮影した写真を選択した時、左上に「JPEG + RAW」と表示されていれば撮影成功です?
以上になります。
それでは
- 投稿日:2021-02-20T15:11:48+09:00
【iOS】RxCocoa,RxSwiftで複雑なTableViewを実装する
RxCocoa,RxSwift
でsection
をつけたり複雑な実装をするときはRxTableViewDataSourceType
を準拠したカスタムUITableViewDataSource
を作成する必要があります。RxTableViewDataSourceTypeを準拠したUITableViewDataSourceの作成
準拠させる際に
NSObject
も準拠させる必要があるところに注意class CustomTableViewDataSource: NSObject, UITableViewDataSource, RxTableViewDataSourceType { // セクションを作る場合は多次元配列にする // Modelは任意のデータ型 typealias Element = [[Model]] var items: Element = [[]] func tableView(_ tableView: UITableView, observedEvent: Event<[[Model]]>) { Binder(self) { dataSource, element in dataSource.items = element tableView.reloadData() } .on(observedEvent) } }セクションの作成
class CustomTableViewDataSource: NSObject, UITableViewDataSource, RxTableViewDataSourceType { // セクションの個数 func numberOfSections(in tableView: UITableView) -> Int { return items.count } // セクション内のcellの個数 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items[section].count } } extension CustomTableView: UITableViewDelegate { // セクションに設定するViewを返す func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return CustomTableHeaderView() } // セクションの高さを返す func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 30 } }セクション内のcellの描画
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.scheduleListCell.identifier, for: indexPath) as! CustomTableViewCell // ここでcellのinitととかを必要であればやる return cell }スワイプでセルの削除などアクション有効にする
cellをスワイプしてアクションを表示させたい場合、
canEditRowAt
を有効にする必要がある。func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return true }TableViewを描画する
前項までで作成したDataSourceを元にTableViewを描画する。
// Modelは任意のデータ var items = BehaviorRelay<[[Model]]>(value: []) let customDataSource = CustomTableViewDataSource() private let disposeBag = DisposeBag() private func observeList() { // itemsとcustomDataSourceを元にtableViewを描画する items .bind(to: tableView.rx.items(dataSource: customDataSource)) .disposed(by: disposeBag) } // delegateを設定 private func setDelegate() { tableView.rx.setDelegate(self) .disposed(by: disposeBag) } // cellの選択 private func observeSelect() { tableView.rx.itemSelected.subscribe(onNext: { [weak self] indexPath in // 選択された時のアクションを記述 }).disposed(by: disposeBag) }
- 投稿日:2021-02-20T14:31:16+09:00
NSOutlineViewの基本的な実装
概要
NSOutlineView
を使って下記のように表示するだけの実装を行う。GitHub
参考
- NSOutlineView on macOS Tutorial
- 主に参考とした
- NSOutlineView 開閉時の挙動を比較してみる - Qiita
- HIG: Outline Views
- 公式ドキュメント: NSOutlineView
実装
Model
- 今回セルに対応されるModelは以下の通り。
NodeType
はそのセルがGroup
なのかを判断するのに使用しているenum NodeType { case group case parent case item } class Node { // MARK: - Properties var title: String var nodeType: NodeType var children: [Node] // MARK: - Lifecycle init(title: String, children: [Node] = [], nodeType: NodeType = .item) { self.title = title self.children = children self.nodeType = nodeType } // MARK: - Helpers var numberOfChildren: Int { children.count } var hasChildren: Bool { !children.isEmpty } }ViewController.swift
NSOutlineViewの設定
- 下記の通り
NSOutlineView
に必要な設定をコード内で行うselectionHighlightStyle
やfloatsGroupRows
の設定で見た目と挙動が大きく変わるので、下記を参考に必要なものを選択すると良いprivate func initializeOutlineView() { // 別のXibファイルから読み込む場合は登録が必要 outlineView.register(MyCellView.nib(inBundle: nil), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: String(describing: MyCellView.self))) outlineView.delegate = self outlineView.dataSource = self outlineView.autosaveExpandedItems = true // スタイルの設定 outlineView.selectionHighlightStyle = .regular outlineView.floatsGroupRows = false nodes.append(contentsOf: Node.createSampleNodes()) outlineView.reloadData() }Starting in OS X version 10.5, passing 'nil' will expand each item under the root in the outline view.
outlineView.expandItem(nil, expandChildren: true)NSOutlineViewDataSource
NSOutlineView
のデータに関するDelegateメソッドを実装する- itemが含むchildrenの数を返す
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { if let node = item as? Node { return node.numberOfChildren } return nodes.count }
- 親に対する子の情報を与える
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { if let feed = item as? Node { return feed.children[index] } return nodes[index] }
- itemが開閉可能か(trueを返すとCellViewにDisclosure Buttonが表示される)
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { if let node = item as? Node { return node.hasChildren } return false }
- itemがグループ行のものかどうか
// グループ行かどうか(NSOutlineViewの設定如何では見た目と挙動が特別となる) func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool { guard let node = item as? Node else { return false } return node.nodeType == .group }NSOutlineViewDelegate
NSOutlineView
のDelegateメソッドを実装する- 各行に表示するCellViewの設定
public func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { guard let myCellView = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: String(describing: MyCellView.self)), owner: self) as? MyCellView, let node = item as? Node else { return nil } myCellView.configureUI(withNode: node) return myCellView }
- 行選択が変更された場合に呼ばれるNotification
func outlineViewSelectionDidChange(_ notification: Notification) { guard let outlineView = notification.object as? NSOutlineView else { return } let selectedIndex = outlineView.selectedRow guard let node = outlineView.item(atRow: selectedIndex) as? Node else { return } print("Selected Title: \(node.title)") }NSTreeControllerの話
NSOutlineView
で調べているとNSTreeController
を使っている例が多くヒットする。- ただ
CocoaBinding
が絡んでいて、私の理解が浅いためちょっと使いこなせなかった。
- ので今回は
NSOutlineView
単体で実装している- イメージに過ぎないが、
NSTreeController
を使うことで煩雑な処理、例えば複数選択のアクション・入れ替え・削除が簡単に扱えると思っている。参考
- Navigating Hierarchical Data Using Outline and Split Views
- Appleのしっかりとした(珍しい)macOSのチュートリアル
- NSTreeController
- The simplest NSTreeController example | Daemon Construction
- 何がなんだかわからんけど、なぜか動くサンプル
- NSTreeController + NSOutlineView: A powerful combination | by Alexander Murphy | Building Ibotta | Medium
- コードベースのバインディングを行っていて比較的理解はしやすい
- NSTreeControllerを使うため、バインディングの基本を知りたいと思い…
- How to Use Cocoa Bindings and Core Data in a Mac App
- CoreDataとバインディング。まだ見れていない。
- NSTreeControllerで使用するObjectの変換についてのメモ — MindTo01s
- NSTreeControllerとNSOutlineviewの相互変換の話
- 投稿日: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-20T06:06:04+09:00
プッシュ通知の許可画面を出す。
『"このアプリ"は通知を送信します。よろしいですか?許可、許可しない』のやつを表示させる。
import UIKit import Firebase import UserNotifications @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { FirebaseApp.configure() //許可画面を出す。 if #available(iOS 10.0, *) { // For iOS 10 display notification (sent via APNS) UNUserNotificationCenter.current().delegate = self let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization( options: authOptions, completionHandler: {_, _ in }) } else { let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) application.registerUserNotificationSettings(settings) } application.registerForRemoteNotifications() } }
- 投稿日:2021-02-20T06:06:04+09:00
プッシュ通知の許可画面を表示する
『"このアプリ"は通知を送信します。よろしいですか?許可、許可しない』のやつを表示させる。
import UIKit import Firebase import UserNotifications @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { FirebaseApp.configure() //許可画面を出す。 if #available(iOS 10.0, *) { // For iOS 10 display notification (sent via APNS) UNUserNotificationCenter.current().delegate = self let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization( options: authOptions, completionHandler: {_, _ in }) } else { let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) application.registerUserNotificationSettings(settings) } application.registerForRemoteNotifications() } }
- 投稿日: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
書いたりって細かい違いのところがまだ把握できてないので、今後の学習の中でつかんでいきたいと思います。