質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1138閲覧

Swift で TextField に取得してきた文字列を入れたい

nyansuke373

総合スコア7

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

1クリップ

投稿2020/05/06 17:09

編集2020/05/07 07:56

Swiftプログラミング初心者です。他のクラスから下記メソッドを呼び出して、TextFieldに取得してきた文字列を入れたいのですができていません。値を表示させるのにはどのようにすればいいのか、ご教授いただけないでしょうか?

目標
ストーリーボードのresultTextViewに、取得してきた文字列であるresultStringを表示したい。
現在
デバッグでresultStringに値が渡ってきていることはわかっていますが、resultTextViewのテキストフィールドに取ってきた値が反映されない。
考察
UITextField型のresultTextViewをString型に直せば良いのでしょうか?
そうであれば直しかたを教えていただきたいです。

①ResultViewController 結果の表示先

Swift

1import UIKit 2 3class ResultViewController: UIViewController, UITextFieldDelegate { 4 5 @IBOutlet weak var resultTextView: UITextField! 6 7 override func viewDidLoad() { 8 super.viewDidLoad() 9 resultTextView.delegate = self 10 } 11 12 func showResult(resultString: String) { 13 resultTextView?.text = resultString  //  ← ここに渡ってくる値です 14 print(resultString) 15 } 16}

②呼び出し元 APIを叩いて得られたデータをJSON解析を行い帰ってきたデータを使っています。

import Alamofire import SwiftyJSON // ViewControllerの初期化 var VC = ViewController() var ResultVC = ResultViewController() public class APIRequest { func HttpRequest(sentence: String) { Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in switch response.result { // 処理成功時 case .success: if let result = response.result.value as? [String: Any] { //SwiftyJSONを使用してJSON解析 let json:JSON = JSON(response.data as Any) var resultString = json["converted"].string ResultVC.showResult(resultString: resultString!)                     //↑ここから呼び出す } // 処理失敗時 case .failure(let error): } } } }

のようにResultViewControllerを呼び出しています。
※もし、呼び出す場所がAlamofireのクロージャー内がまずいのであれば、どのように修正するのが良いでしょうか?

③ViewController

import UIKit class ViewController: UIViewController,UITextFieldDelegate { @IBOutlet weak var inputTextView: UITextField! @IBOutlet weak var outputTextView: UITextField! // APIRequestの初期化 var apiRequest = APIRequest() override func viewDidLoad() { super.viewDidLoad() inputTextView.delegate = self initInputText() } private func initInputText() { inputTextView.text = "" } @IBAction func convertButton(_ sender: UIButton) { //キーボードを閉じる view.endEditing(true) //ここでHttpRequestを呼んでいます apiRequest.HttpRequest(sentence: inputTextView.text!) } }

ViewController(入力後HttpRequestを呼んでいます)→HttpRequest(API取得)→ResultViewController(結果表示)をしたいです。
ViewController
回答ありがとうございます。修正後、(HttpRequestを呼ぶより先にsegueで遷移してしまっているため?)nilでResultViewControllerに渡ってきてしまっている問題が発生しております。
イメージ説明

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TsukubaDepot

2020/05/06 22:56

Alamofireで処理しているクラスと、ShowResultを記述しているクラスの関係はどのようになっていますでしょうか。 気になっている点は2つで、 1. ResultVC.showResult() とやっているが、ResultVC というインスタンスはどのようにして生成したものなのか 2. Alamofireのクロージャ内でshowResultを呼び出してラベルの更新を行っているが、これはもしかしたらうまくいかないかもしれない(ラベルの書き換えはメインスレッドで行う必要があるから) の2点です。 もう少し2つのコードの関係がわかるような情報はいただけないでしょうか。
nyansuke373

2020/05/07 00:03

ご回答ありがとうございます。2つのコードの関係がわかるような情報として追記をさせていただきました。Alamofireのクロージャー内がまずいのであれば、具体的にどのように修正するのが良いでしょうか?
TsukubaDepot

2020/05/07 02:59

追記ありがとうございます。 ところで、APIRequest.HttpRequestはどこで呼び出されるメソッドでしょうか。いただいたソースには呼び出し元がないため、どのタイミングで呼び出されているのかわからない状態です。 また、 var VC = ViewController() var ResultVC = ResultViewController() とやっていますが、ここで2つのViewControllerをインスタンス化している理由は何かありますでしょうか。 StoryBoardベースで行っているのであれば、基本的にこれらの処理は不必要だと思います。
nyansuke373

2020/05/07 04:40

ご回答いただきありがとうございます。記載修正いたしました。 ViewController(入力後HttpRequestを呼んでいます)→HttpRequest(API取得)→ResultViewController(結果表示)を考えています。 インスタンス化の記述については削除いたします。
guest

回答1

0

ベストアンサー

ご希望の処理を実現するためには、下記のことに関する理解が必要だと思います。

  1. 新しい View をインスタンス化して画面遷移する方法
  2. 遷移先のプロパティへの値渡し
  3. クロージャを用いた非同期処理
  4. クロージャとスコープの関係

ソースコードを拝見した感じ、上の項目について混乱が生じているか、あるいは理解が及んでいないところがあるように感じました。

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}

投稿2020/05/07 05:23

TsukubaDepot

総合スコア5086

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nyansuke373

2020/05/07 08:02 編集

ご回答ありがとうございます。学習させていただきます。修正後、(HttpRequestを呼ぶより先にsegueで遷移してしまっているため?)nilでResultViewControllerに渡ってきてしまっている問題が発生しております。対応を考えていただくことはできませんでしょうか。※上記画像参照ください
TsukubaDepot

2020/05/07 08:54

違うビューに値を渡しつつ遷移するための流れは 1. performSegueを実行する 2. prepare で遷移先のビューコントローラを調べ、値をセットする 3. 新しく表示されたビューで値を処理する と言う流れになります。 nilで渡ってきたということは、2の処理がきちんと行われていない可能性が高いと思います。 たとえば、新しくプロジェクトを立ち上げてみて、私が回答につけた処理をそのままコピーし(Alamofire周りは書き直す必要があるかもしれませんが、それ以外はそのままでいいはずです)、部品の紐付けを行なってみて実行してみてはいかがでしょうか。
TsukubaDepot

2020/05/08 05:02

もし、この問題が解決したのであれば、解決済みとして処理していただけますでしょうか。 自己解決という形でも構いませんので、よろしくお願いいたします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問