【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】 スクレイピングで GitHub Contribution Graph をつくる

WEB-API もあるようですが。


❯ curl -s https://github.com/users/benigumocom/contributions

で、HTMLが取得できるというのでやってみる。

わーい。

guard ってなんとなく嫌いだなあ。

👉 GitHub Gist のコードをスクリプトから更新する方法 hatena-bookmark


【SwiftUI】onAppear() ・onChange(initial: true) ・task() の実行順序

プロパティをいじるのに便利ですが。

実行の順序ですよ。

これは、予想通りですが。


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 の表示後にまとめて実行される。

結構、手が止まるんですよね、ここらへん。