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

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

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

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

Q&A

解決済

4回答

2202閲覧

【swift3】URLオブジェクトのアンラップ【URLSession】

A2wwM

総合スコア44

Swift

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

0グッド

0クリップ

投稿2017/02/07 12:50

###前提・実現したいこと

こちらを参考に、こちらのページの15番目のアプリ(swift2で作成)と同じものをswift3で作っているのですが
どうしてもエラーが解決できずに困っています。
教えていただけますと幸いです。

###発生している問題・エラーメッセージ

ボタンを押した時の処理内容にエラーメッセージ「initializer for conditional binding must have Optional type,not 'URL'」が出る

###該当のソースコード

// // ViewController.swift // weather forecast // // Created by on 2017/02/06. // Copyright © 2017年 All rights reserved. // import UIKit class ViewController: UIViewController, UITextFieldDelegate { @IBOutlet weak var textField: UITextField! @IBOutlet weak var resultLabel: UILabel! @IBOutlet weak var cityLabel: UILabel! func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() cityLabel.text = textField.text return true } @IBAction func findweather(_ sender: Any) { super.viewDidLoad() var success = false let attemptedUrl = Foundation.URL(string: "http://www.weather-forecast.com/locations/" + textField.text!.replacingOccurrences(of: "", with: "=") + "/forecasts/latest")! if let url = attemptedUrl { ←ここにエラーが出る let session = URLSession.shared let task = session.dataTask(with: url, completionHandler: { (data, response, error) in if let urlContent = data { let webContent = String(data: urlContent, encoding: String.Encoding.utf8) let websiteArray = webContent?.components(separateBy: "3 Day Weather forecast Summary: </b><spanclass = \"read-more-small\"><span class = \"read-more-content\"> <span class = \"class = \"phrase\">") if websiteArray!.count > 1{ let weatherArray = websiteArray[1].components(separatedBy: "</span>") if weatherArray.count > 1 { let weatherSummary = weatherArray[0].replacingOccurrences(of: "&deg;", with: "°") dispatch_async(dispatch_get_main_queue(), { () -> Void in success = true self.resultLabel.text = weatherSummary }) } } } }) task.resume if success == false { self.resultLabel.text = "Sorry, something went wrong." } } } override func viewDidLoad() { } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { //キーボードを閉じる self.view.endEditing(true) } }

###試したこと
このサイトから、型宣言をする必要があるとわかったのですがダメでした。

そのほか同じようなアプリを作った人のwebサイト等々色々見てみたのですがいずれも解決できませんでした。

###補足情報(言語/FW/ツール等のバージョンなど)
swift3.0
xcode8.2.1
macOS Sierra(バージョン 10.12)

またその他以下の2点もわからなかったので教えていただけますと幸いです。
①キーボードを閉じる処理上部の'textField.resignFirstResponder()'と下部'self.view.endEditing(true)'は違うのか。なぜ2つもキーボードを閉じる処理を書く必要があるのか
②なぜボタン押した処理のところにsuper.viewDidLoad()を置くのか。なぜ下部override func viewDidLoad()からsuper.viewDidLoad()を除くのか。
これは画面の準備ができたときに同時に、findweatherメソッドの他の処理もするということでしょうか?
もしそうなら、viewDidLoadメソッドの中に書けばいいのではと思ってしまうのですが。

質問が多くて恐縮です。

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

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

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

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

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

fuzzball

2017/02/08 04:01 編集

(deleted)
fuzzball

2017/02/07 23:50 編集

②について。なぜ置いたのでしょうか?なぜ除いたのでしょうか?どちらに関しても通常こういうことはしません。
A2wwM

2017/02/08 05:52

ありがとうございます。やっぱり通常はこういうことはしないんですね
guest

回答4

0

swift

1 let attemptedUrl = Foundation.URL(string: "http://www.weather-forecast.com/locations/" + textField.text!.replacingOccurrences(of: "", with: "=") + "/forecasts/latest")! 2

上記コードの最後にある「!」によって、すでにattemptedUrl自体がアンラッップされているからでは?

投稿2017/02/08 01:27

t_obara

総合スコア5488

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

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

A2wwM

2017/02/08 05:54

ありがとうございます! 確かに!笑と思って書き直したんですが、また他のエラーが出るんで、それについて少し調べてみます!
guest

0

ベストアンサー

Xcode 8.2.1でSingle View Applicationの新規プロジェクトを生成して、2017/02/11 17:14付けのソースを動かしてみました。
以下の修正で動きました。

  1. @IBOutlet、@IBAction、textFieldのdelegateの配線を適切に行う。
  2. そのまま動かすとApp Transport Securityに引っかかるので、Allow Arbitrary Loadsの設定を行う。
  3. websiteArray = webContent?.components(separatedBy: ...)の引数を以下のように修正。
let websiteArray = webContent?.components(separatedBy: "3 Day Weather Forecast Summary:</b><span class=\"read-more-small\"><span class=\"read-more-content\"> <span class=\"phrase\">")

挙動を観察したところ、以下のようになりました。

  1. textFieldに地名(例えばTokyo)と入れてReturnキーを押すと、cityLabelにその地名(Tokyo)が表示される。
  2. textFieldに地名(例えばTokyo)と入れてボタンを押すと、resultLabelに最初にSorry, something went wrong.と表示され、しばらく後に天気予報が表示される。

cityLabelが更新される条件(textFieldでReturnキーを押す)とresultLabelが更新される条件(ボタンを押す)が異なること、およびボタンを押すと無条件でエラーメッセージが表示されることに注意してください。

また、天気の取得にHTMLのスクレイピングという手法を使っていますが、HTMLの内容がちょっと変わっただけでプログラムが動かなくなる可能性があり、あまりお勧めできません。

正直な感想を書くと、元のサイトのタイトル通り○○ですね。もっとまともな本(Webサイトでもいいのですが、本の方が信頼できる可能性が高いです)を読んで、1行1行のコードが何をやってるのかをちゃんと理解することをお勧めします。

投稿2017/02/11 11:09

hoshi-takanori

総合スコア7891

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

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

A2wwM

2017/02/12 00:11

ありがとうございましたっ! 無事動かすことができましたっ! ただおっしゃる通りseparatedBy以降の記述がどんな処理をしているのかわからなかったですし、(そもそもきっと本人の忘備録のようなものだとは思うのですが)他のものを参考にやった方がいいのかもしれませんね。 そもそもまだswiftビギナーズクラブのこちらの本(https://www.amazon.co.jp/exec/obidos/asin/4797387149/officialsiteswift-22/)を終えたばかりですし。 Udemyで良さそうなのを見つけたので次はこれををやってみようと思います! https://www.udemy.com/ios10-perfect/ もし他にオススメがありましたら教えてもらえると嬉しいです♪
hoshi-takanori

2017/02/12 04:59

separatedBy: の後ろに書いてあるのは、長いですが単なる一つの文字列です。取得したHTMLの中で天気の直前にたまたま現れる文字列で、HTMLを分割して天気を切り出すのに使ってるだけです。で、その文字列が1文字でも違っていれば切り出せず、うまく動かなかったというわけです。 すでに本はお読みになっていたんですね。目次を見てみましたが、Lessonごとにアプリを作るもののようですね。おそらく、本に書いてある通りにやって一応アプリは動いたけど、なぜ動くのかはいまいちよくわかっていない、という状態ではないでしょうか。 そこから次のステップに進むには、やはりSwift言語をしっかり理解する必要があります。Udemyの講座にはSwift言語の解説もあるようなので、それをやるのもいいと思いますよ。だいぶお安くなってるようですし。 あとは、ビギナーズクラブの本のよく一緒に購入されている商品で紹介されていた「詳細! Swift 3 iPhoneアプリ開発」 https://www.amazon.co.jp/dp/4800711487 にもSwift言語の解説があるようなので、読んでみるといいかもしれません。 (Swift言語の解説書としては昔からある「詳解Swift」や最近出た「Swift実践入門」が評判がいいですが、初心者にはちょっと難しい気がします。)
A2wwM

2017/02/12 10:25

っっっっっ!!ご丁寧にリンクまでありがとうございます泣 1冊読んで大枠はわかったんですけど、やっぱり細かいところ特にAPIとかあまり理解できてなくて... 勉強会参加するくらいで周りに教えてくれるような人いないんで、どれがいいだとか教えてもらえてすごく助かりますっ!次の書籍もどれ買おうか迷ってたところですしorz 教えていただいた書籍見てみます♪
A2wwM

2017/02/13 12:45

お忙しいだろうに色々探してくださってありがとうございますっ! 本屋さんでみてみますねっ
guest

0

tomohiro_obaraさん、hoshi-takanoriさんご回答いただきありがとうございます

投稿2017/02/12 00:16

A2wwM

総合スコア44

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

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

0

さっきのは、tomohiro_obaraさんに指摘して頂いたところを修正した後に以下の変更をしたらエラーが表示されなくなりました。
・separatedByと書くべきところがseparateBy(dが抜けている)になっていたので修正
・ dispatch_async(dispatch_get_main_queue(), { () -> Void inを
DispatchQueue.main.async(execute: { () -> Void inに修正
をそれぞれ行ったら解決できました。
ただ今度はシュミレーターを起動することができても、cityLabelとresultLabelに結果が表示されないのでそちらに取り掛かっています。

投稿2017/02/09 03:49

編集2017/02/09 03:51
A2wwM

総合スコア44

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

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

A2wwM

2017/02/11 08:14

以下が変更後のコードになるのですが、どうにもcityLabelとresultLabelにそれぞれの結果を表示することができません。 教えていただけますと幸いです。 import UIKit class ViewController: UIViewController, UITextFieldDelegate { @IBOutlet weak var textField: UITextField! @IBOutlet weak var resultLabel: UILabel! @IBOutlet weak var cityLabel: UILabel! func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() cityLabel.text = textField.text return true } @IBAction func findweather(_ sender: Any) { super.viewDidLoad() var success = false //URLオブジェクトの生成 let attemptedUrl = Foundation.URL(string: "http://www.weather-forecast.com/locations/" + textField.text!.replacingOccurrences(of: "", with: "-") + "/forecasts/latest") //attemptedUrlをアンラップ //attemptedUrlがnilでないかを確かめる if let url = attemptedUrl { //nilでないなら以下の処理をする //URLSession.sharedで通信を行うオブジェクトを作る let session = URLSession.shared //ダウンロードするデータをdataTask(with:)で指定。 //通信が完了したら(data, response, error)というタプル形式で(ダウンロードしたデータ、ステータスコード、エラーコード)が渡ってくるので、この値を利用してダウンロードしたデータの処理を行う let task = session.dataTask(with: url, completionHandler: { (data, response, error) in //dataがnilでないなら if let urlContent = data { let webContent = String(data: urlContent, encoding: String.Encoding.utf8) let websiteArray = webContent?.components(separatedBy: "3 Day Weather forecast Summary: </b><spanclass = \"read-more-small\"><span class = \"read-more-content\"> <span class = \"class = \"phrase\">") if websiteArray!.count > 1 { let weatherArray = websiteArray?[1].components(separatedBy: "</span>") if (weatherArray?.count)! > 1 { let weatherSummary = weatherArray?[0].replacingOccurrences(of: "&deg;", with: "°") //swift3では「DispatchQueue.main.asynchronously」ではなく、「DispatchQueue.main.async」 DispatchQueue.main.async(execute: { () -> Void in success = true self.resultLabel.text = weatherSummary }) } } } }) task.resume() //エラーの場合の処理内容 if success == false { self.resultLabel.text = "Sorry, something went wrong." } } } override func viewDidLoad() { } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { //キーボードを閉じる self.view.endEditing(true) } }
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.51%

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

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

質問する

関連した質問