Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

ͭͬͨ͘ΞϓϦ

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

SwiftUI

Slide 5

Slide 5 text

SwiftUI • એݴܕγϯλοΫεͳUIϑϨʔϜϫʔΫ • macOS, iOS, iPadOS, tvOS, watchOSͰ࢖͑Δ • XcodeͰͷϓϨϏϡʔ΍σβΠϯαϙʔτ AppKit • MVCͳUIϑϨʔϜϫʔΫ • macOSͰ࢖͑Δ • NSʙͱ͍͏pre fi x͕෇͍͍ͯΔ NBD04ʙ NBD04ʙ /FYU4UFQʜ

Slide 6

Slide 6 text

Ψϫͷ෦෼͸4XJGU6*Ͱ௚઀࡞Εͳ͍ /41PQPWFS 4XJGU6*Ͱ௚઀࡞Εͳ͍ /44UBUVT*UFN/44UBUVT#BS#VUUPO 7JFX 1PQPWFS

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

@NSApplicationDelegateAdaptor import SwiftUI @main struct DemoApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var delegate var body: some Scene { WindowGroup { ContentView() } } } class AppDelegate: NSObject, NSApplicationDelegate { } ΞϓϦͷىಈ௚ޙʹݺ͹ΕΔϝιουͳͲ͕࢖͑ΔΑ͏ʹͳΔ

Slide 11

Slide 11 text

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͕࢖͑Δ ΞϓϦͷىಈ௚ޙʹݺ͹ΕΔϝιου

Slide 12

Slide 12 text

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ͷίϯτϩʔϥ

Slide 13

Slide 13 text

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͔ΒΞΠίϯΛফ͢ ΞϓϦىಈ௚ޙͷ΢Οϯυ΢Λશ෦ফ͢ ΞϓϦҎ֎ͷྖҬͷΫϦοΫͰϙοϓΞοϓ͕ด͡ΔΑ͏ʹ͢Δ ΞϓϦΛΞΫςΟϒʹ͠ͳ͍ͱ͏·͘ด͡ͳ͍

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

SwiftUIΛ؆୯ʹ঺հ

Slide 16

Slide 16 text

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() } }

Slide 17

Slide 17 text

struct ContentView: View { var body: some View { Button { print("pushed") } label: { HStack { Image(systemName: "face.smiling") Text("Hello, world!") } } .padding() } }

Slide 18

Slide 18 text

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() } }

Slide 19

Slide 19 text

Tips

Slide 20

Slide 20 text

ӈΫϦοΫϝχϡʔΛͭ͘Δ @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]) ͕ඞཁ

Slide 21

Slide 21 text

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 } } ςΩετฤूܥͷϏϧτΠϯίϚϯυ܈

Slide 22

Slide 22 text

഑෍

Slide 23

Slide 23 text

ΞϓϦͷ഑෍ (ඇAppStore) • macOS 10.15 (Catalina) Ҏ߱͸Appleͷެূ (Notarization) Λ෇͚ ͍ͯͳ͍ΞϓϦ͸ىಈͤͮ͞Β͘ͳ͍ͬͯΔ • XcodeͰProduct→Archive • OrganizerΛ։͘ • Distribute App → Developer ID → Upload • ͠͹Β͘଴ͭ • Distribute App → Developer ID • Successfully notarizedͱݴΘΕΔͷͰExportΛΫϦοΫ

Slide 24

Slide 24 text

ඇදࣔAppͷ഑৴ • Unlisted app distribution • AppStoreʹ͋Δ͚Ͳ௚ϦϯΫΛ஌Βͳ͍ͱ࢖͑ͳ͍ • 2022೥1݄຤͝Ζ͔Β࢖͑ΔΑ͏ʹͳͬͨ • ࢼͯ͠Έ͍ͨ https://developer.apple.com/jp/support/unlisted-app-distribution/

Slide 25

Slide 25 text

4XJGU6*ͰϝχϡʔόʔΞϓϦΛ࡞ΕΔ