【SwiftUI】シンプルに HTTPリクエスト でお天気情報取得

単純に GET によるレスポンスボディを取得したい。

こんなにシンプルな感じでかけるとは!


let url = URL(string: "https://example.com")!
for try await line in url.lines {
  print(line)
}

👉 task(priority:_:) | Apple Developer Documentation hatena-bookmark

一行ごとに取れるようです。

試してみます。


import SwiftUI
import PlaygroundSupport

struct SampleView: View {
  @State private var message = "Loading..."

  var body: some View {
    Text(message)
      .task {
        message = await getWeather()
      }
  }

  private func getWeather() async -> String {
    let url = URL(string: "https://wttr.in/?format=3")!

    do {
      var lines: [String] = []
      for try await line in url.lines {
        lines.append(line)
      }
      return lines.joined()
    } catch {
      return "Faild to load"
    }
  }
}

PlaygroundPage.current.setLiveView(
  SampleView()
    .frame(width: 300, height: 300)
)

簡単にレスポンスを確認するときに使えそうです。

👉 lines | Apple Developer Documentation hatena-bookmark
👉 chubin/wttr.in: :partly_sunny: The right way to check the weather hatena-bookmark

 

😅 まさか String() でこんなことができるとか


print(
  (try? String(contentsOf: URL(string: "https://wttr.in/?format=3")!)) 
    ?? "Error!"
)

// Kisarazu, Japan: ⛅️  +25°C


【SwiftUI + SwiftData】@Query とは? - MV (Model + View) Pattern

C も P も VM もないんですね!

【SwiftUI + SwiftData】@Query とは? - MV Pattern ( Model + View )

「@Query」を基本的に、どう理解しておくか。


import SwiftUI

// Model
struct Product {

  static var all: [Product] { get async }
}

// View
struct ProductList: View {
  @State private var products: [Product] = []

  var body: some View {
    VStack {

    }
    .task {
      products = await Product.all
    }
  }
}

SwiftData を使うと以下のように書ける。


import SwiftUI
import SwiftData

// Model
struct Product {

}

// View
struct ProductList: View {
  @Query private var products: [Product]

  var body: some View {
    VStack {

    }
  }
}

「モデルデータコレクションの非同期取得」 ですね。

@State と同様に変更を監視しています。

👉 Stop using MVVM for SwiftUI | Apple Developer Forums hatena-bookmark


【SwiftUI】再描画の伝播 - @State と @Binding

なんとく「対」で使う雰囲気だけで使っていた @State と @Binding。

そうでもないらしい。

 

🔄 @Binding はいらない

親のカウンターの値を子に渡す。


struct Parent: View {
  // Left side of mutating operator isn't mutable: 'self' is immutable
  // private var count = 0
  @State private var count = 0

  var body: some View {
    VStack {
      Button("\(count)") {
        count += 1
      }
      Child(count: count)
    }
    .padding()
    .background(.yellow)
  }
}

struct Child: View {
  let count: Int

  var body: some View {
    Text("\(count)")
    .padding()
    .background(.white)
  }
}

どうやら、子で @Binding を付けずに受け取っても再描画される。

@State がなくても伝わる? と思ったが不可。

struct 内のプロパティは var でも immutable

らしい。

 

🔄 @Binding を付ける

親に「$」、子に「@Binding」を付けて渡す。


struct Parent: View {
  @State private var count = 0

  var body: some View {
    VStack {
      Button("\(count)") {
        count += 1
      }
      Child(count: $count) // *
    }
    .padding()
    .background(.yellow)
  }
}

struct Child: View {
  @Binding var count: Int // *

  var body: some View {
    Text("\(count)")
    .padding()
    .background(.white)
  }
}

挙動は、最初と同じ。

あれ、@Binding って何だったの?

 

🔄 子で値を更新する

今度は、子でも受け取った値を更新してみます。


struct Parent: View {
  @State private var count = 0

  var body: some View {
    VStack {
      Button("\(count)") {
        count += 1
      }
      Child(count: $count)
    }
    .padding()
    .background(.yellow)
  }
}

struct Child: View {
  @Binding var count: Int

  var body: some View {
    Button("\(count)") {
      count -= 1 // *
    }
    .padding()
    .background(.white)
  }
}

親も更新されます。

双方向に再描画できるようです。

Jetpack Compose では「単方向」のみですが、その前の Android View でいうところの「View Binding」に似ています。

 

🔄 まとめ

  • struct 内のプロパティは var でも変更できないので @State を付ける。
  • その値の変化は子に再描画を @Binding なしで伝播できる。
  • 子で @Binding で受ければ、さらに親にも伝播できる。

参照してるイメージというべきか。

👉 【SwiftUI】@State を 子 View でどう受けるか 🤔 - @Binding hatena-bookmark