WEB-API もあるようですが。
❯ curl -s https://github.com/users/benigumocom/contributions
で、HTMLが取得できるというのでやってみる。

わーい。

guard ってなんとなく嫌いだなあ。
WEB-API もあるようですが。
❯ curl -s https://github.com/users/benigumocom/contributions
で、HTMLが取得できるというのでやってみる。

わーい。

guard ってなんとなく嫌いだなあ。
プロパティをいじるのに便利ですが。
実行の順序ですよ。
これは、予想通りですが。
Text("hello")
.onAppear {
print("Text onAppear")
}
.onChange(of: true, initial: true) {
print("Text onChange")
}
.task {
print("Text task")
}
// Text onAppear
// Text onChange
// Text task
記述の順序にかかわらず task() は最後に実行されます。
Text("hello")
.task { print("Text task") }
.onAppear { print("Text onAppear") }
.onChange(of: true, initial: true) { print("Text onChange") }
// Text onAppear
// Text onChange
// Text task
onAppear() と .onChange(initial: true) は記述順。
Text("hello")
.task { print("Text task") }
.onChange(of: true, initial: true) { print("Text onChange") }
.onAppear { print("Text onAppear") }
// Text onChange
// Text onAppear
// Text task
では、View のネスト。
Group {
VStack {
Text("hello")
.task { print("Text task") }
.onChange(of: true, initial: true) { print("Text onChange") }
.onAppear { print("Text onAppear") }
}
.task { print(" VStack task") }
.onChange(of: true, initial: true) { print(" VStack onChange") }
.onAppear { print(" VStack onAppear") }
}
.task { print("Group task") }
.onChange(of: true, initial: true) { print("Group onChange") }
.onAppear { print("Group onAppear") }
// Text onChange
// Text onAppear
// VStack onChange
// VStack onAppear
// Group onChange
// Group onAppear
// Text task
// VStack task
// Group task

task() は 最上位の View の表示後にまとめて実行されることに驚きです。
- ネストの深いものから順番に実行される。
- onAppear(), onChange(initial: true) は記述順に実行される。
- task() は最上位 View の表示後にまとめて実行される。
結構、手が止まるんですよね、ここらへん。
iOS17でクロージャ引数が1つのものは Deprecated と警告が出たりするので、


👉 onChange(of:perform:) | Apple Developer Documentation
こんなかんじで書いてました。
Text("\(count)")
.onChange(of: token) { _, newValue in
// do something ...
}
クソですね !
クロージャの引数なしと2つのものがあるんですね !
func onChange<V>(
of value: V,
initial: Bool = false,
_ action: @escaping () -> Void
) -> some View where V : Equatable
👉 onChange(of:initial:_:) | Apple Developer Documentation
func onChange<V>(
of value: V,
initial: Bool = false,
_ action: @escaping (V, V) -> Void
) -> some View where V : Equatable
👉 onChange(of:initial:_:) | Apple Developer Documentation
よって、引数なしのほうを使って、
フツーに以下のように書けば良かったのか。
Text("\(count)")
.onChange(of: token) {
print("\(token)")
}
なるほど。
IDE の選択肢の一番上をなんとなく選択してたのだろうと思います。

きちんと、ドキュメントを見ることって大事。
考えてみると、引数2つの記述
Text("\(count)")
.onChange(of: token) { oldValue, newValue in
// do something ...
}
は、oldValue が必要でない限り使う必要がないな。
この id を書き換えることで task のクロージャ内の処理を繰り返すことができるんですね !
onAppear と同様に初回だけだと思ってましたわ。
func task<T>(
id value: T,
priority: TaskPriority = .userInitiated,
_ action: @escaping () async -> Void
) -> some View where T : Equatable
id
The value to observe for changes. The value must conform to the Equatable protocol.
👉 task(id:priority:_:) | Apple Developer Documentation
公開されている無料現在時刻取得のAPIを使います。
# curl "http://worldtimeapi.org/api/timezone/Asia/Tokyo"
{
"abbreviation": "JST",
"client_ip": "2675:6780:4e0:3200:a181:c317:3902:c121",
"datetime": "2024-03-26T22:10:13.824894+09:00",
"day_of_week": 2,
"day_of_year": 86,
"dst": false,
"dst_from": null,
"dst_offset": 0,
"dst_until": null,
"raw_offset": 32400,
"timezone": "Asia/Tokyo",
"unixtime": 1711458613,
"utc_datetime": "2024-03-26T13:10:13.824894+00:00",
"utc_offset": "+09:00",
"week_number": 13
}
クライアント側はシンプルな実装にしておきます。
let url = URL(string: "https://worldtimeapi.org/api/timezone/Asia/Tokyo.txt")!
var lines: [String] = []
for try await line in url.lines {
lines.append(line)
}
print(String(lines[2].components(separatedBy: "T")[1]))
// 22:10:13.824894+09:00
👉 【SwiftUI】シンプルに HTTPリクエスト でお天気情報取得
task (id:) を使っておいて、ボタンで id を更新させます。

「id」って便利な使えるやつなんですね !
@Query て何となく直感で使ってたけど、記述が豊富ですよね。
@Query(sort: \Snippet.creationDate, order: .reverse)
var snippets: [Snippet]
@Query(sort: [SortDescriptor(\Snippet.creationDate)])
var allSnippets: [Snippet]
@Query(
filter: #Predicate<Snippet> { $0.isFavorite },
sort: [SortDescriptor(\.creationDate)]
)
var favoriteSnippets: [Snippet]
static var fetchDescriptor: FetchDescriptor<Recipe> {
let descriptor = FetchDescriptor<Recipe>(
predicate: #Predicate { $0.isFavorite == true },
sortBy: [.init(\.createdAt)]
)
descriptor.fetchLimit = 10
return descriptor
}
@Query(FavoriteRecipesList.fetchDescriptor) private var favoriteRecipes: [Recipe]
ドキュメント見てみると、書き方が多すぎ。

👉 Query() | Apple Developer Documentation
どれもデータ取得時の操作をしてるのだろうけど、
上手に使えるように整理しておきたい。
ドキュメントから引数まわりをすべて抽出して眺めながら並べ替えてみる。
Query<Value, Element>(filter: Predicate<Element>?, sort: KeyPath<Element, Value>, order: SortOrder, transaction: Transaction?)
Query<Value, Element>(filter: Predicate<Element>?, sort: KeyPath<Element, Value?>, order: SortOrder, transaction: Transaction?)
Query<Value, Element>(filter: Predicate<Element>?, sort: KeyPath<Element, Value>, order: SortOrder, animation: Animation)
Query<Value, Element>(filter: Predicate<Element>?, sort: KeyPath<Element, Value?>, order: SortOrder, animation: Animation)
Query<Element>(filter: Predicate<Element>?, sort: [SortDescriptor<Element>], transaction: Transaction?)
Query<Element>(filter: Predicate<Element>?, sort: [SortDescriptor<Element>], animation: Animation)
Query<Element>(FetchDescriptor<Element>, transaction: Transaction?)
Query<Element>(FetchDescriptor<Element>, animation: Animation)
Query(transaction: Transaction)
Query(animation: Animation)
Transaction、Animation を除くと、引数は、
filter: Predicate<Element>
sort: KeyPath<Element, Value>
order: SortOrder
sort: [SortDescriptor<Element>]
FetchDescriptor<Element>
の5つであることがわかります。
上のサンプルコードからも、直感的に、
Predicate → 抽出条件のフィルター (filter)
KeyPath → 並び替えのキー (sort)
SortOrder → 並び替えの順序 (order)
は意味が分かりますが。
「Descriptor」
とは何なのか。
「並び替えのキーと順序の入れ物」のようです。
並び替え順序は省略できます。
@Query(
sort: [
SortDescriptor(\Movie.title),
SortDescriptor(\Movie.releaseYear, order: .reverse)
]
)
var movies: [Movie]
SortDescriptor に入れることで、複数の並び替えのキーを配列に入れて @Query にセットできるようになります。
なので、細かい並び替えの指定ができます。
👉 SortDescriptor | Apple Developer Documentation
「抽出条件の Predicate と 並び替えの配列 [SortDescriptor] の入れ物」です。
init(
predicate: Predicate<T>? = nil,
sortBy: [SortDescriptor<T>] = []
)
👉 init(predicate:sortBy:) | Apple Developer Documentation
なので、@Query に入れる抽出条件と並び替えの指定は、すべてこれに入れることができるようになります。
しかし、@Query より ModelContext.fetch() で使われる方が多い印象。
👉 FetchDescriptor | Apple Developer Documentation
ざっくりそれぞれの関係を表でまとめておきます。

以下、GitHub 公開コードより抜粋列挙。
@Query を使う場合は、大体どれかと似た形になりそうです。
@Query private var birds: [Bird]
@Query(sort: \Backyard.creationDate)
private var backyards: [Backyard]
@Query(
filter: #Predicate<BirdFood> { $0.id == "Nutrition Pellet" }
)
private var birdFood: [BirdFood]
@Query(
filter: #Predicate<Episode> {$0.finishedPlaying != true },
sort: [
SortDescriptor(\Episode.pubDate, order: .reverse)
]
)
var episodes: [Episode]
@Query(
sort: [
SortDescriptor(\Station.order),
SortDescriptor(\Station.stationName)
]
)
private var stations: [Station]
@Query(
FetchDescriptor<Album>(
predicate: #Predicate { $0.parentAlbum == nil },
sortBy: [SortDescriptor<Album>(\.name)]
)
)
var albums: [Album]
基本的な操作はこれだけでいけるかな。無理かな。