Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swift UIで始めるmacOSメニューバーアプリ

Swift UIで始めるmacOSメニューバーアプリ

3e77f9dbec6a87756d1dbdddab283aee?s=128

Nulab Inc.
PRO

April 22, 2022
Tweet

More Decks by Nulab Inc.

Other Decks in Technology

Transcript

  1. 2022೥4݄22೔ NuCon 2022 Spring দຊ༟ೋ Swift UIͰ࢝ΊΔ macOSϝχϡʔόʔΞϓϦ

  2. ͭͬͨ͘ΞϓϦ

  3. FreeeͷۈଵΞϓϦ ग़ۈͱୀۈΛߦ͏͚ͩͷγϯϓϧͳΞϓϦ (ψʔϥϘࣾ಺͚ͩͰ഑෍த)

  4. SwiftUI

  5. SwiftUI • એݴܕγϯλοΫεͳUIϑϨʔϜϫʔΫ • macOS, iOS, iPadOS, tvOS, watchOSͰ࢖͑Δ •

    XcodeͰͷϓϨϏϡʔ΍σβΠϯαϙʔτ AppKit • MVCͳUIϑϨʔϜϫʔΫ • macOSͰ࢖͑Δ • NSʙͱ͍͏pre fi x͕෇͍͍ͯΔ NBD04ʙ NBD04ʙ /FYU4UFQʜ
  6. Ψϫͷ෦෼͸4XJGU6*Ͱ௚઀࡞Εͳ͍ /41PQPWFS 4XJGU6*Ͱ௚઀࡞Εͳ͍ /44UBUVT*UFN/44UBUVT#BS#VUUPO 7JFX 1PQPWFS

  7. ϝχϡʔόʔͷΫϦοΫͰ SwiftUIͷϏϡʔΛϙοϓΞοϓͤ͞Δ·Ͱ

  8. None
  9. DemoApp.swift import SwiftUI @main struct DemoApp: App { var body:

    some Scene { WindowGroup { ContentView() } } } import SwiftUI struct ContentView: View { var body: some View { Text("Hello, world!") .padding() } } ContentView.swift
  10. @NSApplicationDelegateAdaptor import SwiftUI @main struct DemoApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var

    delegate var body: some Scene { WindowGroup { ContentView() } } } class AppDelegate: NSObject, NSApplicationDelegate { } ΞϓϦͷىಈ௚ޙʹݺ͹ΕΔϝιουͳͲ͕࢖͑ΔΑ͏ʹͳΔ
  11. class AppDelegate: NSObject, NSApplicationDelegate { } private var statusItem: NSStatusItem?

    func applicationDidFinishLaunching(_ notification: Notification) { statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) let button = statusItem!.button! button.image = NSImage(systemSymbolName: "leaf", accessibilityDescription: nil) } 4'4ZNCPMT͕࢖͑Δ ΞϓϦͷىಈ௚ޙʹݺ͹ΕΔϝιου
  12. class AppDelegate: NSObject, NSApplicationDelegate { } private var statusItem: NSStatusItem?

    private var popover: NSPopover? func applicationDidFinishLaunching(_ notification: Notification) { statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) let button = statusItem!.button! button.image = NSImage(systemSymbolName: "leaf", accessibilityDescription: nil) button.action = #selector(showPopover) } @objc func showPopover(_ sender: NSStatusBarButton) { if popover == nil { let popover = NSPopover() popover.contentViewController = NSHostingController(rootView: ContentView()) self.popover = popover } popover?.show(relativeTo: sender.bounds, of: sender, preferredEdge: NSRectEdge.maxY) } 4XJGU6*Λϗετͯ͘͠ΕΔ"QQ,JUͷίϯτϩʔϥ
  13. class AppDelegate: NSObject, NSApplicationDelegate { } private var statusItem: NSStatusItem?

    private var popover: NSPopover? func applicationDidFinishLaunching(_ notification: Notification) { NSApp.windows.forEach{ $0.close() } NSApp.setActivationPolicy(.accessory) statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) let button = statusItem!.button! button.image = NSImage(systemSymbolName: "leaf", accessibilityDescription: nil) button.action = #selector(showPopover) } @objc func showPopover(_ sender: NSStatusBarButton) { if popover == nil { let popover = NSPopover() popover.contentViewController = NSHostingController(rootView: ContentView()) popover.behavior = .transient popover.animates = false self.popover = popover } popover?.show(relativeTo: sender.bounds, of: sender, preferredEdge: NSRectEdge.maxY) NSApp.activate(ignoringOtherApps: true) } ΞϓϦىಈதʹ%PDL͔ΒΞΠίϯΛফ͢ ΞϓϦىಈ௚ޙͷ΢Οϯυ΢Λશ෦ফ͢ ΞϓϦҎ֎ͷྖҬͷΫϦοΫͰϙοϓΞοϓ͕ด͡ΔΑ͏ʹ͢Δ ΞϓϦΛΞΫςΟϒʹ͠ͳ͍ͱ͏·͘ด͡ͳ͍
  14. private var statusItem: NSStatusItem? @main struct DemoApp: App { #if

    os(macOS) @NSApplicationDelegateAdaptor(AppDelegate.self) var delegate #endif var body: some Scene { WindowGroup { ContentView() } } } #if os(macOS) class AppDelegate: NSObject, NSApplicationDelegate { … } #endif
  15. SwiftUIΛ؆୯ʹ঺հ

  16. ContentView.swift import SwiftUI struct ContentView: View { var body: some

    View { Text("Hello, world!") .padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
  17. struct ContentView: View { var body: some View { Button

    { print("pushed") } label: { HStack { Image(systemName: "face.smiling") Text("Hello, world!") } } .padding() } }
  18. struct ContentView: View { @State var point: CGFloat = -1

    var body: some View { let gradient = LinearGradient(gradient: Gradient(colors: [.blue, .green, .blue]), startPoint: UnitPoint(x: point, y: 0), endPoint: UnitPoint(x: 1.0 + point, y: 0)) VStack { Capsule() .fill(gradient) .animation(Animation.linear(duration: 2) .repeatForever(autoreverses: false), value: point) .onAppear { point = 1 } .frame(width: 200, height: 10, alignment: .leading) .padding() Button { print("pushed") } label: { HStack { Image(systemName: "face.smiling") Text("Hello, world!") } } } .padding() } }
  19. Tips

  20. ӈΫϦοΫϝχϡʔΛͭ͘Δ @objc func showPopover(_ sender: NSStatusBarButton) { guard let event

    = NSApp.currentEvent else { return } if event.type == NSEvent.EventType.rightMouseUp { let menu = NSMenu() menu.addItem( withTitle: NSLocalizedString("Preference", comment: "Show preferences window"), action: #selector(openPreferencesWindow), keyEquivalent: "" ) menu.addItem(.separator()) menu.addItem( withTitle: NSLocalizedString("Quit", comment: "Quit app"), action: #selector(terminate), keyEquivalent: "" ) statusItem?.popUpMenu(menu) return } ɿ ࣄલʹ button.sendAction(on: [.leftMouseUp, .rightMouseUp]) ͕ඞཁ
  21. TextFieldͳͲͰΩʔϘʔυγϣʔτΧοτΛ༗ޮʹ͢Δ @main struct FreeeApp: App { #if os(macOS) @NSApplicationDelegateAdaptor(AppDelegate.self) var

    delegate #endif var body: some Scene { #if os(macOS) WindowGroup { // empty }.commands { TextEditingCommands() } Settings { SettingsView() } #else WindowGroup { ContentView(store: AppStore()) } #endif } } ςΩετฤूܥͷϏϧτΠϯίϚϯυ܈
  22. ഑෍

  23. ΞϓϦͷ഑෍ (ඇAppStore) • macOS 10.15 (Catalina) Ҏ߱͸Appleͷެূ (Notarization) Λ෇͚ ͍ͯͳ͍ΞϓϦ͸ىಈͤͮ͞Β͘ͳ͍ͬͯΔ

    • XcodeͰProduct→Archive • OrganizerΛ։͘ • Distribute App → Developer ID → Upload • ͠͹Β͘଴ͭ • Distribute App → Developer ID • Successfully notarizedͱݴΘΕΔͷͰExportΛΫϦοΫ
  24. ඇදࣔAppͷ഑৴ • Unlisted app distribution • AppStoreʹ͋Δ͚Ͳ௚ϦϯΫΛ஌Βͳ͍ͱ࢖͑ͳ͍ • 2022೥1݄຤͝Ζ͔Β࢖͑ΔΑ͏ʹͳͬͨ •

    ࢼͯ͠Έ͍ͨ https://developer.apple.com/jp/support/unlisted-app-distribution/
  25. 4XJGU6*ͰϝχϡʔόʔΞϓϦΛ࡞ΕΔ