【SwiftUI】View の 強制再描画

ProgressView を使って簡単なインジケーターをつくる。


struct AutoProgressView: View {
  @State private var value = 0.0
  private let total = 10.0
  private let duration = 1.0

  var body: some View {
    ProgressView(value: value, total: total)
      .task {
        while value < total {
          try? await Task.sleep(for: .seconds(duration))
          value += duration
        }
      }
  }
}

便利ですねこれ。

👉 ProgressView | Apple Developer Documentation hatena-bookmark

しかし、これを、再スタートしようとすると、

できない。どうやるのこれ。


struct TestView: View {
  var body: some View {

    AutoProgressView()

    Button("restart") {
      // ?
    }

  }
}

たいそうにタイマーまで付けてやりたくない。

View をリフレッシュするだけでいい。

 

■ 強制再描画

「View に id を付けてそれを更新すればいい」らしい。

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


struct TestView: View {
  @State var id = false

  var body: some View {

    AutoProgressView()
      .id(id)

    Button("restart") {
      id.toggle()
    }

  }
}

 

■ まとめ

さすが、先人先生。

ありがとうございます。

👉 [SwiftUI] ViewのIdentityと再描画を意識しよう hatena-bookmark

ちなみに、task の中身のループは、スリープなしで破棄時に消化されているようです。


【Swift】公式サンプル Logger の使い方


print("debug: \(value)")

とかではつらいのかな。

と思いつつ抽出して使ってみる。

👉 Code search results hatena-bookmark


import OSLog
private let logger = Logger(subsystem: "BackyardBirds", category: "BackyardBirdsPassStatus")


logger.log("""
  Processing transaction ID \(unsafeTransaction.id) for \
  \(unsafeTransaction.productID)
""")


logger.debug("""
  Transaction ID \(t.id) for \(t.productID) is verified
""")


logger.error("""
  Transaction ID \(t.id) for \(t.productID) is unverified: \(error)
""")


logger.info("Providing updated status to data generation")

出力レベルがあるんですね。

出力は、


2023-09-16 18:26:11.842691+0900 Develop[99999:999999] [Standard] Debug: Uhooi
2023-09-16 18:26:11.842722+0900 Develop[99999:999999] [Standard] Info: Uhooi
2023-09-16 18:26:11.842766+0900 Develop[99999:999999] [Standard] Notice: Uhooi
2023-09-16 18:26:11.842811+0900 Develop[99999:999999] [Standard] Error: Uhooi
2023-09-16 18:26:11.842858+0900 Develop[99999:999999] [Standard] Fault: Uhooi

{Timestamp} {Library}[{PID}:{TID}] [{Category}] {Message} の形式で出力されています。

👉 os.Loggerの説明と使い方(Swift) #Swift - Qiita hatena-bookmark

なるほど。

 

■ やってみる


import OSLog
private let logger = Logger(subsystem: "subsystem", category: "category")

// ... 

  static func create(modelContainer: ModelContainer) {
    
    logger.debug("debug Creating service instance.")
    logger.trace("trace Creating service instance.")
    logger.info("infoCreating service instance.")
    logger.notice("notice Creating service instance.")
    logger.error("error Creating service instance.")
    logger.warning("warning Creating service instance.")
    logger.fault("fault Creating service instance.")
    logger.critical("critical Creating service instance.")

    shared = SoundDataService(modelContainer: modelContainer)
  }


debug Creating service instance.
trace Creating service instance.
infoCreating service instance.
notice Creating service instance.
error Creating service instance.
warning Creating service instance.
fault Creating service instance.
critical Creating service instance.

あれ、タイムスタンプ的なのデフォルトで出ないのか。

subsystem とか category とかも何の意味があるのか。

print() で良くね?

なんなのこれ。

あ、これか。

extension 化しとくのが楽かもしれません。


extension OSLog {
  static let ui = Logger(subsystem: "com.satoriku.OSLog", category: "ui")
  static let network = Logger(subsystem: "com.satoriku.OSLog", category: "network")
  static let viewCycle = Logger(subsystem: "com.satoriku.OSLog", category: "viewcycle")
}

👉 【Xcode/Swift】OSLogを使ってアプリログを出力する方法(ロギング) - iOS-Docs hatena-bookmark


import Foundation
import os

extension Logger {
    private static var subsystem = Bundle.main.bundleIdentifier!
    static let segue = Logger(subsystem: subsystem, category: "segue")
    static let note = Logger(subsystem: subsystem, category: "note")
    static let flashcard = Logger(subsystem: subsystem, category: "flashcard")
    static let deck = Logger(subsystem: subsystem, category: "deck")
    static let settings = Logger(subsystem: subsystem, category: "settings")
    static let option = Logger(subsystem: subsystem, category: "option")
}

👉 Flashcard-Adder/Flashcard Adder/Logger.swift at bfa5f3526fba48f5c6596f92d7648e4137fa60c9 · Nonameentered/Flashcard-Adder hatena-bookmark
👉 OSLog and Unified logging as recommended by Apple - SwiftLee hatena-bookmark

👉 Logger | Apple Developer Documentation hatena-bookmark


【SwiftUI】computed property vs func - computed property を上手に使いたい

なんとなく使っていた computed property と func。

気にはなっていたので。

以下の条件に当てはまる場合は、functionよりpropertyを使う方がよい。

- 例外を投げない
- 計算量が少ない(または初回実行時にキャッシュされる)。
- オブジェクトの状態が変化していない場合、何度呼び出しても同じ結果を返す。

(O(1)ではないcomputed propertyではその旨をドキュメント(コメント)に明記すること。 プロパティアクセスは一般的に計算コストが安価だと見なされるので、そうでない場合はその旨の明記が必要である。)

👉 [Swift] FunctionとComputed Propertyの使い分けの基準 #Swift - Qiita hatena-bookmark

* Top Hightlight
A property expresses an inherent quality of an instance, while a method performs an action.

プロパティはインスタンスの固有の品質を表現し、メソッドはアクションを実行します。

👉 Functions vs Computed property — What to use? | by Aaina jain | Swift India | Medium hatena-bookmark

うむむ、具体的にどう考えたらいいのか。

Apple 公式サンプルをさらう。


struct BackyardsSearchSuggestions: View {
  @Query private var backyards: [Backyard]
    
  var events: [BackyardVisitorEvent] {
    Set(backyards.compactMap(\.currentVisitorEvent))
      .sorted { ($0.backyard?.name ?? "") < ($1.backyard?.name ?? "") }
      .sorted { ($0.bird?.speciesName ?? "") < ($1.bird?.speciesName ?? "") }
    }
    
  var body: some View {
    ForEach(events) { event in

👉 sample-backyard-birds/Multiplatform/Backyards/BackyardsSearchSuggestions.swift at main · apple/sample-backyard-birds hatena-bookmark


struct BirdFoodPickerSheet: View {
  var backyard: Backyard
    
  @Query(sort: [.init(\BirdFood.priority, order: .reverse), .init(\BirdFood.name, comparator: .localizedStandard)])
  private var birdFood: [BirdFood]
    
  @Environment(\.dismiss) private var dismiss
  @State private var presentingBirdFoodShop = false
    
  private let metrics = BirdFoodStoreMetrics.birdFoodStore

  var premiumFood: [BirdFood] {
    birdFood.filter(\.isPremium)
  }
    
  var otherFood: [BirdFood] {
    birdFood.filter { !$0.isPremium }
  }

👉 sample-backyard-birds/Multiplatform/Backyards/BirdFoodPickerSheet.swift at main · apple/sample-backyard-birds hatena-bookmark


struct BirdsSearchSuggestions: View {
  @Query private var birds: [Bird]
    
  var speciesNames: [String] {
    Set(birds.map(\.speciesName)).sorted()
  }
    
  var body: some View {
    ForEach(speciesNames, id: \.self) { speciesName in

👉 sample-backyard-birds/Multiplatform/Birds/BirdsSearchSuggestions.swift at main · apple/sample-backyard-birds hatena-bookmark


struct PlantsSearchSuggestions: View {
  @Query private var plants: [Plant]
    
  var speciesNames: [String] {
    Set(plants.map(\.speciesName)).sorted()
  }
    
  var body: some View {
    ForEach(speciesNames, id: \.self) { speciesName in

👉 sample-backyard-birds/Multiplatform/Plants/PlantsSearchSuggestions.swift at main · apple/sample-backyard-birds hatena-bookmark


struct BackyardBirdsPassShop: View {
  @Environment(\.dismiss) private var dismiss
  @Environment(\.passIDs.group) private var passGroupID
  @Environment(\.passStatus) private var passStatus
    
  private var showPremiumUpgrade: Bool {
    passStatus == .individual || passStatus == .family
  }

👉 sample-backyard-birds/Multiplatform/Shop/BackyardBirdsPassShop.swift at main · apple/sample-backyard-birds hatena-bookmark


struct BackyardsSearchSuggestions: View {
  @Query private var backyards: [Backyard]
    
  var events: [BackyardVisitorEvent] {
    Set(backyards.compactMap(\.currentVisitorEvent))
      .sorted { ($0.backyard?.name ?? "") < ($1.backyard?.name ?? "") }
      .sorted { ($0.bird?.speciesName ?? "") < ($1.bird?.speciesName ?? "") }
  }
    
  var body: some View {
    ForEach(events) { event in

👉 sample-backyard-birds/Multiplatform/Backyards/BackyardsSearchSuggestions.swift at main · apple/sample-backyard-birds hatena-bookmark

View 内の利用状況だけをみると、

ほぼ @Query の加工にしか使ってない。

逆に言えば

@Query の加工には Computed Property が使える。

ということは言えそう。

ちなみに、View 内に func はほとんど見当たらない。