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

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

ただいまの
回答率

90.04%

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

解決済

回答 4

投稿

  • 評価
  • クリップ 0
  • VIEW 1,086

A2wwM

score 10

前提・実現したいこと

こちらを参考に、こちらのページの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メソッドの中に書けばいいのではと思ってしまうのですが。

質問が多くて恐縮です。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • fuzzball

    2017/02/08 08:46 編集

    (deleted)

    キャンセル

  • fuzzball

    2017/02/08 08:50 編集

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

    キャンセル

  • A2wwM

    2017/02/08 14:52

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

    キャンセル

回答 4

+2

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/08 14:54

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

    キャンセル

checkベストアンサー

+1

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/12 19:25

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

    教えていただいた書籍見てみます♪

    キャンセル

  • 2017/02/13 08:57

    次の書籍ですが、こちらの記事
    http://a244.hateblo.jp/entry/2016/11/14/000541
    によると、「詳細! Swift 3 iPhoneアプリ開発」 はプログラミング経験者向けらしいので、「iPhoneアプリ開発講座 はじめてのSwift」
    https://www.amazon.co.jp/dp/4797385286
    の方がいいかも知れません。イラストがたくさんあるみたいだし。

    キャンセル

  • 2017/02/13 21:45

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

    キャンセル

-1

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/11 17: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)

    }
    }


    キャンセル

-1

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 90.04%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る