AWS EC2 인스턴스를 외부 DNS에 연결하기 (탄력적 IP)
[소소한 개발 일지] SwiftUI 기반 macOS 애플리케이션의 타이틀바 영역에 커스텀 뷰 추가하기
2024-11-16
Explanation
오늘은 간단하게 SwiftUI를 사용하는 MacOS 애플리케이션 윈도우의 툴바 영역(타이틀바 영역)에 커스텀 뷰를 추가하는 방법에 대하여 적어보려합니다.
이 글에 사용되는 샘플 코드들은 아래의 리포지토리에서 모두 확인 하실 수 있습니다.
링크: https://github.com/falsy/blog-post-example/tree/main/macOS-project/toolbarView
전 아직 조금씩 공부하고 있는 단계라, 글 내용에 잘못 설명된 부분이 있을 수 있습니다!!
SwiftUI에서 툴바 영역은 toolbar라는 뷰 빌더 메서드를 사용해서 커스텀할 수 있는데요.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import SwiftUI struct ContentView: View { var body: some View { VStack { Text("Content") } .frame(maxWidth: .infinity, maxHeight: .infinity) .toolbar { ToolbarItem() { Button("Toolbar") {} } } } } |
하지만, 이 toolbar 영역을 구성할 수 있는 뷰는 제한적인데요. 일반적으로 툴바 영역 내부에는 버튼을 사용하고, 버튼의 위치를 제어하는 정도의 기능만 제공하는 거 같아요.
아마도 SwiftUI의 다양한 OS를 모두 지원하는 특징 때문에 AppKit에 비해서 제공되는 기능이 제한적이고, 또 아무래도 iOS의 시장이 크다보니 우선 iOS 중심으로 만들고 있어서 더 그렇지 않을까 싶어요.
VStack이나 HStack처럼 뷰 요소를 추가할 수도 있긴한데, 결정적으로 “.frame(maxWidth: .infinity, maxHeight: .infinity)” 처럼 뭐라고 표현해야 할까요.. 음.. 윈도우 크기에 따른 꽉찬 영역을 설정할 수 없습니다. 그리고 그 밖에도 기대한 것과 다르게 동작하는 경우가 많습니다!
저는 그래서 이전까지 전체 너비를 GeometryReader를 사용해서 윈도우 사이즈가 변경될 때마다 뷰 영역의 너비 값을 직접 변경해 주는 식으로 구현했답니다.. 이마저도 macOS v14까지는 괜찮았는데, v15부터는 약간 생각과 다르게 동작하는 부분이 많아서 수정이 필요했습니다.
이제 구현 방법에 대해 이야기해 보자면, 일단 SwiftUI만으로는 구현할 수 있는 방법은 아직 방법이 없는 것 같았고 AppKit을 사용해서 구현할 수 있었습니다.
우선 SwiftUI 기본 윈도우를 사용하지 않고 AppDelegate를 사용해서 새 윈도우를 호출해 줍니다.
1 2 3 4 5 6 7 8 9 10 11 12 |
import SwiftUI @main struct toolbarViewApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { Settings { EmptyView() } } } |
AppDelegate를 선언하기 앞서 툴바 영역에 사용할 뷰와 뷰 컨트롤러를 먼저 만들어 줄게요.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 툴바에 사용할 뷰 import SwiftUI struct CustomToolbarView: View { var body: some View { VStack { Text("Toolbar") .foregroundStyle(.white) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.black) } } |
1 2 3 4 5 6 7 8 9 |
// 툴바에 사용할 컨트롤뷰 import SwiftUI class ToolbarViewController: NSTitlebarAccessoryViewController { override func loadView() { let hostingView = NSHostingView(rootView: CustomToolbarView()) self.view = hostingView } } |
AppKit에서 제공하는 NSTitlebarAccessoryViewController 클래스를 사용해서 툴바 영역에 커스텀 뷰를 추가할 수 있습니다. 저는 SwiftUI를 사용해서 뷰를 만들고 NSHostingView를 사용해서 뷰를 추가해주었습니다.
이제 마지막으로 AppDelegate에 추가를 해주면!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import SwiftUI class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { let windowRect = NSRect(x: 0, y: 0, width: 800, height: 400) let newWindow = NSWindow(contentRect: windowRect, styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false) let contentView = ContentView() .frame(minWidth: 400, maxWidth: .infinity, minHeight: 200, maxHeight: .infinity) newWindow.contentView = NSHostingController(rootView: contentView).view newWindow.makeKeyAndOrderFront(nil) let accessoryViewController = ToolbarViewController() accessoryViewController.layoutAttribute = .top newWindow.addTitlebarAccessoryViewController(accessoryViewController) newWindow.center() let windowController = NSWindowController(window: newWindow) windowController.showWindow(self) } } |
여기서 중요한 부분은 이 부분입니다.
1 2 3 |
let accessoryViewController = ToolbarViewController() accessoryViewController.layoutAttribute = .top newWindow.addTitlebarAccessoryViewController(accessoryViewController) |
이렇게 애플리케이션 윈도우의 툴바 영역에 커스텀 뷰를 구현할 수 있답니다.
사소한 팁??을 추가하자면, 아래와 같이 코드를 추가해서 기본 타이틀바 영역을 숨길 수 있고
1 2 3 4 5 6 7 8 9 10 |
import SwiftUI class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { ... newWindow.titlebarAppearsTransparent = true newWindow.titleVisibility = .hidden } } |
그리고 위와 같은 기본 구성에서는 왼쪽 상단의 닫기, 최소화, 최대화 버튼과 윈도우와의 여백?이 가까운데요. 아래와 같이 빈 툴바를 추가해주면 간격을 조금 넓혀줄 수 있습니다.
1 2 3 4 5 6 7 8 9 |
import SwiftUI class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { ... newWindow.toolbar = NSToolbar() } } |
오늘의 포스팅은 NSTitlebarAccessoryViewController 클래스만 알았으면 간단한 구현인데..
저는 이걸 몰라서, 구현하는 데 몇 개월 걸렸다는 사실은 안비밀..
Advertisement
광고 수익금은 소년소녀가정에 기부됩니다.