Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Swift UIで始めるmacOSメニューバーアプリ
Search
株式会社ヌーラボ
PRO
April 22, 2022
Technology
1.2k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Swift UIで始めるmacOSメニューバーアプリ
株式会社ヌーラボ
PRO
April 22, 2022
More Decks by 株式会社ヌーラボ
See All by 株式会社ヌーラボ
配布資料_ヌーラボの情シスがおすすめする_問い合わせ対応を削減するBacklog活用術.pdf
nulabinc
PRO
0
2
Bits Agent Builder の⼊⾨と活⽤事例
nulabinc
PRO
0
540
Getting Started with Bits Agent Builder: Real-World Use Cases
nulabinc
PRO
0
58
The_Evolution_of_Bits_AI_SRE.pdf
nulabinc
PRO
0
360
進化するBits AI SREと私と組織
nulabinc
PRO
4
940
実践 Datadog MCP Server
nulabinc
PRO
4
900
全社横断PjM⽀援チーム “PEaS”の取り組みと プロジェクトマネジメント でのAI活⽤について
nulabinc
PRO
0
220
Datadog の RBAC のすべて
nulabinc
PRO
4
890
Datadog Live Tokyo 2025登壇資料
nulabinc
PRO
0
200
Other Decks in Technology
See All in Technology
Head First モブプログラミング / Head First Mobprogramming
takaking22
10
12k
なぜ Temporal の大小比較には compare しかないのか / Why Does Temporal Only Have compare() for Comparisons
kazukihayase
1
200
MIXIで活躍できるエンジニアを 若手社員目線で考えてみる
mixi_engineers
PRO
0
130
アクセスキーが漏れた日にやるべきこと- 無効化の先にある本当の対応
kazzpapa3
0
240
作って理解するCoding Agent 〜フレームワークに頼らないピュア Python での実装〜
takapy
3
1.1k
トークンマネジメントでAIにとって働きやすい環境を実現する
hikaruegashira
0
140
同じWAFが、攻撃の“形”は弾く── 正当な“形”の不正は通す
kuroneko13
0
270
自宅NWにISR4331を導入してみた話
okaits
0
120
猫付きpingコマンドを自作
uyuki234
0
290
[potatotips #96] Give Your AI Agent the Flutter Playbook
korodroid
0
150
NANDでも描画したい!
nichica906
3
570
平文パスワードはログに“残り” ── 肝心の侵入は“痕跡すら残らない”
kuroneko13
0
120
Featured
See All Featured
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Making the Leap to Tech Lead
cromwellryan
135
10k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.5k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
10k
Build your cross-platform service in a week with App Engine
jlugia
234
19k
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
450
4 Signs Your Business is Dying
shpigford
187
23k
The Spectacular Lies of Maps
axbom
PRO
1
940
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
300
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.4k
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
430
Transcript
20224݄22 NuCon 2022 Spring দຊ༟ೋ Swift UIͰ࢝ΊΔ macOSϝχϡʔόʔΞϓϦ
ͭͬͨ͘ΞϓϦ
FreeeͷۈଵΞϓϦ ग़ۈͱୀۈΛߦ͏͚ͩͷγϯϓϧͳΞϓϦ (ψʔϥϘ͚ࣾͩͰத)
SwiftUI
SwiftUI • એݴܕγϯλοΫεͳUIϑϨʔϜϫʔΫ • macOS, iOS, iPadOS, tvOS, watchOSͰ͑Δ •
XcodeͰͷϓϨϏϡʔσβΠϯαϙʔτ AppKit • MVCͳUIϑϨʔϜϫʔΫ • macOSͰ͑Δ • NSʙͱ͍͏pre fi x͕͍͍ͯΔ NBD04ʙ NBD04ʙ /FYU4UFQʜ
Ψϫͷ෦4XJGU6*Ͱ࡞Εͳ͍ /41PQPWFS 4XJGU6*Ͱ࡞Εͳ͍ /44UBUVT*UFN/44UBUVT#BS#VUUPO 7JFX 1PQPWFS
ϝχϡʔόʔͷΫϦοΫͰ SwiftUIͷϏϡʔΛϙοϓΞοϓͤ͞Δ·Ͱ
None
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
@NSApplicationDelegateAdaptor import SwiftUI @main struct DemoApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var
delegate var body: some Scene { WindowGroup { ContentView() } } } class AppDelegate: NSObject, NSApplicationDelegate { } ΞϓϦͷىಈޙʹݺΕΔϝιουͳͲ͕͑ΔΑ͏ʹͳΔ
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͕͑Δ ΞϓϦͷىಈޙʹݺΕΔϝιου
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ͷίϯτϩʔϥ
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͔ΒΞΠίϯΛফ͢ ΞϓϦىಈޙͷΟϯυΛશ෦ফ͢ ΞϓϦҎ֎ͷྖҬͷΫϦοΫͰϙοϓΞοϓ͕ด͡ΔΑ͏ʹ͢Δ ΞϓϦΛΞΫςΟϒʹ͠ͳ͍ͱ͏·͘ด͡ͳ͍
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
SwiftUIΛ؆୯ʹհ
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() } }
struct ContentView: View { var body: some View { Button
{ print("pushed") } label: { HStack { Image(systemName: "face.smiling") Text("Hello, world!") } } .padding() } }
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() } }
Tips
ӈΫϦοΫϝχϡʔΛͭ͘Δ @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]) ͕ඞཁ
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 } } ςΩετฤूܥͷϏϧτΠϯίϚϯυ܈
ΞϓϦͷ (ඇAppStore) • macOS 10.15 (Catalina) Ҏ߱Appleͷެূ (Notarization) Λ͚ ͍ͯͳ͍ΞϓϦىಈͤͮ͞Β͘ͳ͍ͬͯΔ
• XcodeͰProduct→Archive • OrganizerΛ։͘ • Distribute App → Developer ID → Upload • ͠Βͭ͘ • Distribute App → Developer ID • Successfully notarizedͱݴΘΕΔͷͰExportΛΫϦοΫ
ඇදࣔAppͷ৴ • Unlisted app distribution • AppStoreʹ͋Δ͚ͲϦϯΫΛΒͳ͍ͱ͑ͳ͍ • 20221݄͝Ζ͔Β͑ΔΑ͏ʹͳͬͨ •
ࢼͯ͠Έ͍ͨ https://developer.apple.com/jp/support/unlisted-app-distribution/
4XJGU6*ͰϝχϡʔόʔΞϓϦΛ࡞ΕΔ