ご希望の処理を実現するためには、下記のことに関する理解が必要だと思います。
- 新しい View をインスタンス化して画面遷移する方法
- 遷移先のプロパティへの値渡し
- クロージャを用いた非同期処理
- クロージャとスコープの関係
ソースコードを拝見した感じ、上の項目について混乱が生じているか、あるいは理解が及んでいないところがあるように感じました。
1.については、APIRequest
で次に表示したいビューをインスタンス化していますが、この方法では画面を表示することはできません。簡単に行うには segue
と組み合わせて performSegue
を使うか、あるいは instantinateViewController
を使い、navigationController
に push する方法を使います。
2.については、インスタンス化したビューのメソッドを使ってセットしていますが、ビューのライフサイクルを考えるとこの方法が適切か疑問です。よく行うのはとりあえずプロパティに値をセットし、viewDidLoad
あたりで実際にラベルに反映させる方法です。
3.は Alamofire に限らずメインスレッドを一旦離れて処理を行う場合には、そのことを見越した処理を行う必要があります。できれば、クロージャ内部は必要最低限の処理を行い、それ以上複雑な処理を行いたい場合には切り分けて処理を行った方がいいかと思います。
4.については、クロージャは周辺環境のプロパティやメソッドを扱えるというメリットはあるものの、その範囲を超えてアクセスすることは当然できません。たとえば、Alamofireの処理内で ViewController
のメソッドである performSegue
を呼ぶことはできません。なので、これも見越した処理を考える必要があります。
とりあえず、下記にこちらで動くように編集してみたソースコードを掲載します。
編集の関係上、3つのクラスを一つのファイルにしていますが、実行される場合には適宜ファイルに分割してもらえますでしょうか(分割しなくても動きますが、Swiftらしくないと言われるかもしれません)。
もし、わからないことがあればコメントください。場合によっては新しい質問にされた方が良いかと思いますが、関連する内容であれば随時コメントします。
swift
1import UIKit
2import Alamofire
3import SwiftyJSON
4
5// MARK: 削除
6// イニシャライザで View をインスタンス化しても表示されないので削除
7// ViewControllerの初期化
8// var VC = ViewController()
9// var ResultVC = ResultViewController()
10
11public class APIRequest {
12 // MARK: 変更
13 // 関数の引数としてクロージャを取るように設定し、クロージャ内部で目的とするビューに表示させる
14 // クロージャは completion: の後に引数として取る
15 // func HttpRequest(sentence: String) {
16 func HttpRequest(sentence: String, completion: @escaping (String) -> Void ) {
17 let url = URL(string: "https://labs.goo.ne.jp/api/hiragana")!
18 let parameters = ["app_id":"適切にセット",
19 "sentence":"(sentence)",
20 "output_type":"hiragana"]
21 // MARK: 一時修正
22 // Alamofire5 を使って検証したので、5以外であれば必要に応じて修正
23 AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in
24 // 以下がオリジナル
25 // Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in
26 switch response.result {
27 // 処理成功時
28 case .success(let data):
29 if let result = data as? [String: Any] {
30 //SwiftyJSONを使用してJSON解析
31 let json = JSON(result as Any)
32 let resultString = json["converted"].string
33 // MARK: 変更
34 // ResultVC.showResult(resultString: resultString!)
35 // 上記の方法でビューをインスタンス化しても表示することはできない。
36 // その他諸々の問題があるので、インスタンス化する方法はクロージャで渡された処理に任せる
37 if let resultString = resultString {
38 // クロージャで渡された処理がメインキューで行うべき処理かもしれないので、メインキューで処理する
39 DispatchQueue.main.async {
40 // クロージャの処理
41 completion(resultString)
42 }
43 }
44
45 }
46 // 処理失敗時
47 case .failure(let error):
48 print("error: ", error.localizedDescription)
49 }
50 }
51 }
52}
53
54class ViewController: UIViewController,UITextFieldDelegate {
55
56 @IBOutlet weak var inputTextView: UITextField!
57 @IBOutlet weak var outputTextView: UITextField!
58 // APIRequestの初期化
59 var apiRequest = APIRequest()
60
61 override func viewDidLoad() {
62 super.viewDidLoad()
63
64 inputTextView.delegate = self
65 initInputText()
66 }
67
68 private func initInputText() {
69 inputTextView.text = ""
70 }
71
72 @IBAction func convertButton(_ sender: UIButton) {
73 //キーボードを閉じる
74 view.endEditing(true)
75
76 // MARK: 変更
77 // リクエストを渡す時にクロージャも渡し、その内部で書き換えの処理を行う
78 // クロージャの処理は completion: の後の {} の中
79 apiRequest.HttpRequest(sentence: inputTextView.text!, completion: {
80 result in
81 // navigationController を使った画面遷移は、performSegue もしくは instantinateViewController と pushViewController を組み合わせて使う
82
83 // segue を使う場合 -> prepare が必要
84 // segue の Identifier に "nextSegue" という名前をつけておく
85 // prepare の sender: には result を渡す
86 self.performSegue(withIdentifier: "nextSegue", sender: result)
87
88 // instantinateViewController を使う場合 -> prepare は不必要
89
90 // if let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "Result") as? ResultViewController {
91 // ResultViewController クラスにある result というプロパティに値を渡す
92 // nextVC.result = result
93 // navigationController にいまインスタンス化したビューをプッシュする
94 // self.navigationController?.pushViewController(nextVC, animated: true)
95 // }
96 })
97 }
98
99 // MARK: 追加
100 // segue を使って値渡しする場合
101 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
102 // 遷移先が ResultViewController の場合
103 if let nextVC = segue.destination as? ResultViewController {
104 // sender が String としてダウンキャストできる場合
105 if let result = sender as? String {
106 // 遷移先のプロパティに値をセットする
107 nextVC.result = result
108 }
109 }
110 }
111}
112
113class ResultViewController: UIViewController, UITextFieldDelegate {
114 // MARK: 追加
115 // 前の画面から渡される変数
116 var result: String!
117
118 @IBOutlet weak var resultTextView: UITextField!
119
120 override func viewDidLoad() {
121 super.viewDidLoad()
122 resultTextView.delegate = self
123 // MARK: 追加
124 // ここで showResult を実行する
125 showResult(resultString: result)
126 }
127
128 func showResult(resultString: String) {
129 resultTextView?.text = resultString // ← ここに渡ってくる値です
130 print(resultString)
131 }
132}