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 株式会社ヌーラボ
The_Evolution_of_Bits_AI_SRE.pdf
nulabinc
PRO
0
340
進化するBits AI SREと私と組織
nulabinc
PRO
4
780
実践 Datadog MCP Server
nulabinc
PRO
4
790
全社横断PjM⽀援チーム “PEaS”の取り組みと プロジェクトマネジメント でのAI活⽤について
nulabinc
PRO
0
190
Datadog の RBAC のすべて
nulabinc
PRO
4
840
Datadog Live Tokyo 2025登壇資料
nulabinc
PRO
0
170
How to Migrate Your Backlog Free Plan
nulabinc
PRO
0
250
チームワークマネジメント Bar #5
nulabinc
PRO
0
93
Geeks Who Drink Fukuoka - 202508
nulabinc
PRO
0
71
Other Decks in Technology
See All in Technology
アラート調査向けAIエージェントの本番導入とその後/AI Agents for Alert Investigation: Production Deployment and After
taddy_919
1
310
プライバシー保護の理論と実践
lycorptech_jp
PRO
1
220
AIペネトレーションテスト・ セキュリティ検証「AgenticSec」紹介資料
laysakura
2
7.9k
「勝手に広まる」人気 AI エージェントを爆速で作ろう!(AWS Summit Japan 2026講演資料)
minorun365
PRO
10
2.8k
デジタル・デザイン構想 by Sayaka Ishizuka
y150saya
0
170
起点・思考・出力で分解する 〜PM業務の自動化設計〜
kazu_kichi_67
2
1.2k
本当の”仕事”を手放せる未来が見えた
mu7889yoon
0
210
攻撃者がいなくてもAIエージェントはインシデントを起こす
nomizone
0
180
テスト設計の本質を改めて考えてみる~生成AIを活用する時代だからこそ、作ったテストの説明性を高めよう~
yamasaki696
1
260
AWS Summit 2026で見えたSIerにとっての Amazon Quickの位置づけ
maf_0521
0
150
SRE歴2ヶ月でも開発6年の知見を活かして、チームで止まっていた環境改善を前に進めた話
a_ono
0
170
【FinOps】データドリブンな意思決定を目指して
z63d
3
570
Featured
See All Featured
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
630
Done Done
chrislema
186
16k
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.1k
Marketing to machines
jonoalderson
1
5.5k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.6k
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.3k
The Spectacular Lies of Maps
axbom
PRO
1
840
Crafting Experiences
bethany
1
200
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.5k
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.5k
What does AI have to do with Human Rights?
axbom
PRO
1
2.2k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.2k
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*ͰϝχϡʔόʔΞϓϦΛ࡞ΕΔ