【SwiftUI】#Preview with @Binding arguments

#Preview@Binding の引数を持つ View をどう書くか。

サンプルとして以前のコードを使います。


.constant() を使うか、@State + return を使うか。


#Preview(".constant(\"\")") {
  TestSearchTextField(text: .constant(""))
    .frame(width: 300)
}

#Preview(".constant(\"dog\")") {
  TestSearchTextField(text: .constant("dog"))
    .frame(width: 300)
}

#Preview("return") {
  @State var text = "dog"
  return TestSearchTextField(text: $text)
    .frame(width: 300)
}

View パーツの確認をすばやく #Preview で確認できるようになりました !


【SwiftUI】色を付ける記述、ややこしすぎませんか?

目指すのはこれ。

緑色の四角の中に、

青色の文字と

オレンジ色の白文字ボタンを置くだけ。

どう書いてますか、色付けの記述。


.foregroundColor()
.foregroundStyle()
.foreground()
.backgroundStyle()
.background() 
.tint() 
// ...

意外と整理できてない私。

 

🤔 四角

VStack を使うとして。


VStack {
}
.frame(width: 100, height: 200)
//.backgroundStyle(.green) // no effect
.background(.green)

シンプルな単色であれば .background()

 

🤔 文字


Text("Hello")
  //.foregroundColor(.blue) // deprecated
  .foregroundStyle(.blue)
  //.tint(.blue) // no effect

将来 deprecated となるのは避けます。

.foreground() は使えないし suggestion にも表示されない。

公式サンプルをみても、

Text の色付けはすべて .foregroundStyle() となっていました。

 

🤔 ボタン

意外と悩みます。

文字とボタン表面の色、

押したり離したりしたときの色の変化、

角の形、

と要素が多いです。

それぞれに使えそうな modifier も複数あります。

多く試して、使いやすものを定型として覚えておきたいです。

まずは、角が丸いボタンなので .buttonStyle(.bordered) を使います。


Button("World") {
}
//.foregroundColor(.white) // deprecated
.foregroundStyle(.white)
//.backgroundStyle(.orange) // no effect
.buttonStyle(.bordered)
//.background(.orange) // break border
//.backgroundStyle(.orange) // no effect
//.tint(.orange) // only border

色と形とエフェクトを同時に満たすことができませんでした。

.tint() でいけそうに思えましたが、

きっちりとしたオレンジが表面に付きません。

次は .buttonStyle(.borderedProminent) を使います。

「prominent」の意味は、

prominent 形
1.〔周囲より〕高くなった、突き出した
2. 人目を引く、目立つ、派手な
3. 有名な、著名な、優れた、卓越した

なので、「目立つ突き出たボタン」ということですね。


Button("World") {
}
.buttonStyle(.borderedProminent)
//.foregroundStyle(.white) // no need default color
//.background(.orange) // wrong background
//.backgroundStyle(.orange) // no effect
.tint(.orange)

これですね !

ボタンを押したときの変化もいいかんじです。

label や makebody などを使おうとも思いましたが、

不必要に長くなりそうなのでやめておきました。

 

🤔 まとめ

最終的にこう書けました。


VStack {
  Text("Hello")
    .foregroundStyle(.blue)
  Button("World") {
  }
  .buttonStyle(.borderedProminent)
  .tint(.orange)
}
.frame(width: 100, height: 100)
.background(.green)

色付けのざっくりシンプルイメージとして、


// テキストやシンボル
.foregroundStyle()

// ボタン表面
.buttonStyle(.borderedProminent)
.tint()

// 固定の背景色
.background()

ぐらいから書き始めるのが良さげに思います。

それで意図通りにいかないときはさらに考えるかんじで。

すべてを覚えられないので簡単なものから順番に。


【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