【Swift】ログイン時に起動する - SMAppService


import Foundation
import ServiceManagement
import os.log

@available(macOS 13.0, *)
public enum LaunchOnLogin {
  public static var isEnabled: Bool {
    get { SMAppService.mainApp.status == .enabled }
    set {
      do {
        if newValue {
          if SMAppService.mainApp.status == .enabled {
            try? SMAppService.mainApp.unregister()
          }
          try SMAppService.mainApp.register()
        } else {
          try SMAppService.mainApp.unregister()
        }
      } catch {
        os_log("Failed to \(newValue ? "enable" : "disable") launch at login: \(error.localizedDescription)")
      }
    }
  }
}

👉 Rectangle/Rectangle/LaunchOnLogin.swift at 277cadc905d9ff92c7948a5c97794141f8d1277c · rxhanson/Rectangle hatena-bookmark


import AppKit
import ServiceManagement

@available(macOS 13.0, *)
class LoginItemManager {
  func loginItemIsEnabled() -> Bool {
    return SMAppService.mainApp.status == .enabled
  }

  func disableLoginItem() {
    try? SMAppService.mainApp.unregister()
  }

  func enableLoginItem() {
    try? SMAppService.mainApp.register()
  }
}

👉 phpmon/phpmon/Common/Helpers/LoginItemManager.swift at 4a3dee3c5023ef252e5dd48de4d2937087eb941b · nicoverbruggen/phpmon hatena-bookmark


import ServiceManagement

public extension SMAppService {
  var isEnabled: Bool {
    status == .enabled
  }

  func toggle() throws {
    isEnabled ? try unregister() : try register()
  }
}

👉 LunarBar/LunarBarMac/Modules/Sources/AppKitExtensions/SMAppService+Extension.swift at e7ae92a8fca7895d739686839afa450fd60663de · LunarBar-app/LunarBar hatena-bookmark

 

■ まとめ

以上を参考に、こう書きました。


@AppStorage("launchAtLogin") private var isOn = false

Toggle("Launch at login", isOn: $isOn)
  .onAppear {
    isOn = SMAppService.mainApp.status == .enabled
  }
  .onChange(of: isOn) {
    let app = SMAppService.mainApp
    try? isOn ? app.register() : app.unregister()
  }

その後、結局、

@AppStorage も @State も持たないようにしました。



登録状態は、


[Settings] - [General] - [Login Items]

でOS側設定画面からも管理できます。

しかし、双方向のバインディングはつらくないですか。

👉 SMAppService | 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 はほとんど見当たらない。


【 Swift 】2次元配列と1次元配列の相互変換 🤔

こんな12年前の so の書き込み。


p.x = index / 3;
p.y = index % 3;

👉 c# - Convert 1D array index to 2D array index - Stack Overflow hatena-bookmark

さすが、先人先生。

やってみました。

長ったらしいですが考え方を思い出したいときのために。

 

🤔 配列をn個ずつに分割する

副産物としてこんなかんじに書けました。


extension Array {
  func chunked(by: Int) -> [[Element]] {
    return  (0 ..< (self.count / by)).map {
      Array(self[$0 * by ..< ($0 + 1) * by])
    }
  }
}

print(
  [1, 2, 3, 4, 5, 6].chunked(by: 3)
)
// [[1, 2, 3], [4, 5, 6]]

 

🤔 まとめ

配列の変換


let d2: [[String]] = [
  ["あ", "い", "う", "え", "お"],
  ["か", "き", "く", "け", "こ"],
  ["さ", "し", "す", "せ", "そ"]
]

// 2次元から1次元 *
let d1 = d2.flatMap { $0 }
print(d1)
// ["あ", "い", "う", "え", "お", "か", "き", "く", "け", "こ", "さ", "し", "す", "せ", "そ"]

print(
  // 1次元から2次元 *
  (0 ..< d1.count / numCols).map {
    Array(d1[($0 * numCols) ..< ($0 + 1) * numCols])
  }
)
// [["あ", "い", "う", "え", "お"], ["か", "き", "く", "け", "こ"], ["さ", "し", "す", "せ", "そ"]]

extension 化する。


extension Array where Element: Collection { 
  func toD1() -> [Element.Element] { // nested
    return self.flatMap { $0 } 
  }
}

extension Array {
  func toD2(numCols: Int) -> [[Element]] {
    return (0 ..< self.count / numCols).map {
      Array(self[($0 * numCols) ..< ($0 + 1) * numCols])
    }
  }
}

1次元配列インデックスと2次元配列座標の関係


index = y * numCols + x

x = index % numCols
y = index / numCols

SwiftData の inMemory で実装してみたが遅かったので、@Observable クラスへ。

👉 【 SwiftUI 】 Pong Wars を SwiftUI に移植してみた hatena-bookmark

しかし、正直、勉強すればするほど謎が増えます !


👉 【Swift】2次元配列 で 転置行列 ( transpose matrix ) hatena-bookmark




Xcode の自動ビルドのせいで編集できないのだが - How to disable Automatically Refresh Canvas and background build compile

Xcode の自動ビルドというかコンパイルというか。

手動にしたいんですけど。

自動で裏でなんかしら動いて、

エディターを触るのに詰まる感じ。

ロジックをいじるときには非常にストレス。

 

😩 Settings → General → Show live issues OFF

良くわからない。

ON でも OFFでも何がどう変わるのか。

ググるとやたらヒットする。

👉 xcode 9 how to disable auto build … | Apple Developer Forums hatena-bookmark

 

😩 Editor → Canvas → Automatically Refresh Canvas OFF

You can turn off the auto-compile feature for previews/canvas'. When having a SwiftUI View open in the editor go to the Editor -> Canvas -> Automatically Refresh Canvas.

👉 Stop auto compile on Xcode preview - Stack Overflow hatena-bookmark

欲しかったのはこれかな?

ファイルを編集すると、Canvas 上に


「Preview paused 🔃」

と表示される。

🔃 を押さないと、自動でビルドは行われない。

これだわ !

 

😩 まとめ

Xcode が裏で動いてコード編集できないときは、


Editor

  ↓

Canvas 

  ↓

Automatically Refresh Canvas OFF

です。

リフレッシュするときのショートカットは、


⌥ (Option) + ⌘ (Command) + P

のようですです。

しかし、そもそも、

編集操作を妨げてまで

デフォルトで自動でリフレッシュする必要なくね?

初心者はつらい。いちいち詰まる。


【Swift】2次元配列 で 転置行列 ( transpose matrix )

これやりたくなるときありますよね。

転置行列(てんちぎょうれつ、英: transpose [of a matrix], transposed matrix)とは、m 行 n 列の行列 A に対して A の (i, j) 要素と (j, i) 要素を入れ替えてできる n 行 m 列の行列のことである。転置行列は tA, AT, A⊤, Atr, A′ などと示される。行列の転置行列を与える操作のことを転置(てんち、英: transpose)といい、「A を転置する」などと表現する

👉 転置行列 - Wikipedia hatena-bookmark

これを、変換すると、


[
  [1, 2, 3, 4, 5], 
  ["A", "B", "C", "D", "E"],
  ["あ", "い", "う", "え", "お"],
  ["か", "き", "く", "け", "こ"]
]

こうなるやつ。


[
  [1, "A", "あ", "か"],
  [2, "B", "い", "き"],
  [3, "C", "う", "く"],
  [4, "D", "え", "け"],
  [5, "E", "お", "こ"]
]

Swift で、extension で、やってみます。

 

🔄 配列系のプロトコルは多すぎないか

Xcode の反応をみながら、とりあえずいけた。

縦横インデックスを

きれいに .indecies で取りたかったけど、

うまく取れなかった。


extension Collection where Element: Collection,
                           Self.Index == Int, Element.Index == Int {

  func transposed1() -> [[Element.Element]] {
    let cols = 0 ..< (self.first?.count ?? 0)
    let rows = 0 ..< self.count
    var result: [[Element.Element]] = []
    for col in cols {
      var newRow: [Element.Element] = []
      for row in rows {
        newRow.append(self[row][col])
      }
      result.append(newRow)
    }
    return result
  }

}

// [[1, "A", "あ", "か"], [2, "B", "い", "き"], [3, "C", "う", "く"], [4, "D", "え", "け"], [5, "E", "お", "こ"]]

Array とか Collection。

where 句 や Element。

書きながらでないと、きっと理解できない感じがする。

👉 Collection | Apple Developer Documentation hatena-bookmark

 

🔄 for ループ を map に

「空配列を作成して要素追加」てのがなんとなくだるいので、

map を使います。

入れ子なので「$0」は使いません。


func transposed2() -> [[Element.Element]] {
  let cols = 0 ..< (self.first?.count ?? 0)
  let rows = 0 ..< self.count
  return cols.map { col in
    rows.map { row in
      self[row][col]
    }
  }
}

すぐに、return から始めたいので、

最初の let を省略。


func transposed3() -> [[Element.Element]] {
  return (0 ..< (first?.count ?? 0)).map { col in
    (0 ..< count).map { row in
      self[row][col]
    }
  }
}

ここまででいいか。

 

🔄 まとめ

まとめておきます。

また、勉強したら更新します。

Swift の「プロトコル」ってなんか高級。

👉 あなたの知らないCollectionの世界 #Swift - Qiita hatena-bookmark