Mac uygulamaları, mobil dünyadan farklı bir evren. Çoklu pencereler, menu bar, keyboard shortcuts, drag & drop, file system erişimi... iOS'ta alışık olmadığın birçok kavram var. Ama SwiftUI sayesinde, aynı dili kullanarak masaüstü uygulamalar geliştirebilirsin.
İçindekiler
- macOS vs iOS: Temel Farklar
- İlk macOS Uygulaması
- Multi-Window Desteği
- Menu Bar ve Toolbar
- Document-Based Apps
- AppKit Entegrasyonu
- Menu Bar Only Apps
- Mac Catalyst: iOS'tan macOS'a
- Production Best Practices
macOS vs iOS: Temel Farklar {#farklar}
Özellik | iOS | macOS |
|---|---|---|
**Pencere** | Tek | Çoklu |
**Navigation** | Push/modal | Sidebar + split view |
**Input** | Touch | Mouse + keyboard |
**File system** | Sandbox | Genişletilmiş erişim |
**Menu** | Yok | Menu bar zorunlu |
**Min boyut** | Sabit | Kullanıcı ayarlar |
**Dock** | Yok | App icon + badge |
Dış Kaynaklar:
İlk macOS Uygulaması {#ilk-uygulama}
swift
1@main2struct MyMacApp: App {3 var body: some Scene {4 WindowGroup {5 ContentView()6 }7 .windowStyle(.titleBar)8 .windowToolbarStyle(.unified(showsTitle: true))9 .defaultSize(width: 900, height: 600)10 .commands {11 // Custom menu komutları12 CommandGroup(replacing: .newItem) {13 Button("Yeni Proje") { createProject() }14 .keyboardShortcut("n")15 }16 }17 18 // Settings penceresi19 Settings {20 SettingsView()21 }22 }23}24 25struct ContentView: View {26 @State private var selectedItem: SidebarItem?27 28 var body: some View {29 NavigationSplitView {30 // Sidebar - macOS'un kalbi31 List(selection: $selectedItem) {32 Section("Projeler") {33 ForEach(projects) { project in34 Label(project.name, systemImage: "folder")35 .tag(project as SidebarItem?)36 }37 }38 Section("Etiketler") {39 ForEach(tags) { tag in40 Label(tag.name, systemImage: "tag")41 .tag(tag as SidebarItem?)42 }43 }44 }45 .listStyle(.sidebar)46 .frame(minWidth: 200)47 } detail: {48 if let selectedItem {49 DetailView(item: selectedItem)50 } else {51 ContentUnavailableView(52 "Bir öğe seçin",53 systemImage: "sidebar.left",54 description: Text("Sol panelden bir proje veya etiket seçin")55 )56 }57 }58 .toolbar {59 ToolbarItem(placement: .primaryAction) {60 Button(action: {}) {61 Label("Ekle", systemImage: "plus")62 }63 }64 }65 }66}Multi-Window Desteği {#multi-window}
swift
1@main2struct MultiWindowApp: App {3 var body: some Scene {4 // Ana pencere5 WindowGroup {6 MainView()7 }8 9 // Ayrı pencere türü10 WindowGroup("Detay", id: "detail", for: UUID.self) { $itemId in11 if let itemId {12 DetailWindow(itemId: itemId)13 }14 }15 .defaultSize(width: 500, height: 400)16 .defaultPosition(.trailing)17 18 // Utility pencere19 Window("Inspector", id: "inspector") {20 InspectorView()21 }22 .defaultSize(width: 300, height: 600)23 .windowStyle(.titleBar)24 .windowResizability(.contentSize)25 }26}27 28// Pencere açma29struct MainView: View {30 @Environment(\.openWindow) var openWindow31 32 var body: some View {33 Button("Detay Aç") {34 openWindow(id: "detail", value: selectedItem.id)35 }36 }37}Menu Bar ve Toolbar {#menu-bar}
swift
1// Custom Menu Commands2struct AppCommands: Commands {3 @FocusedBinding(\.selectedDocument) var document4 5 var body: some Commands {6 // File menüsüne ekleme7 CommandGroup(after: .newItem) {8 Button("Şablondan Oluştur...") {9 // Template picker aç10 }11 .keyboardShortcut("n", modifiers: [.command, .shift])12 }13 14 // Custom menü15 CommandMenu("Proje") {16 Button("Derle") { }17 .keyboardShortcut("b")18 Button("Çalıştır") { }19 .keyboardShortcut("r")20 Divider()21 Button("Temizle") { }22 .keyboardShortcut("k", modifiers: [.command, .shift])23 }24 25 // Toolbar26 ToolbarCommands()27 SidebarCommands()28 }29}30 31// Keyboard shortcuts32struct EditorView: View {33 var body: some View {34 TextEditor(text: $content)35 .keyboardShortcut(.defaultAction) // Enter = default action36 .onKeyPress(.escape) {37 dismiss()38 return .handled39 }40 }41}Menu Bar Only Apps {#menubar-apps}
swift
1@main2struct MenuBarApp: App {3 var body: some Scene {4 // Sadece menu bar'da yaşayan app5 MenuBarExtra("My Utility", systemImage: "bolt.fill") {6 VStack(spacing: 12) {7 Text("CPU: 45%")8 Text("RAM: 8.2 GB")9 Text("Disk: 120 GB free")10 11 Divider()12 13 Button("Detaylar...") { openDetailWindow() }14 Button("Çıkış") { NSApplication.shared.terminate(nil) }15 .keyboardShortcut("q")16 }17 .padding()18 }19 .menuBarExtraStyle(.window) // Popup pencere20 }21}Document-Based Apps {#document-based}
macOS'un en güçlü özelliklerinden biri document-based uygulama desteği. SwiftUI ile dosya tabanlı uygulamalar oluşturmak oldukça kolay:
swift
1// 1. Document tipi tanımla2struct TextDocument: FileDocument {3 static var readableContentTypes: [UTType] { [.plainText] }4 5 var text: String6 7 init(text: String = "") {8 self.text = text9 }10 11 init(configuration: ReadConfiguration) throws {12 guard let data = configuration.file.regularFileContents,13 let text = String(data: data, encoding: .utf8)14 else {15 throw CocoaError(.fileReadCorruptFile)16 }17 self.text = text18 }19 20 func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {21 let data = text.data(using: .utf8)!22 return FileWrapper(regularFileWithContents: data)23 }24}25 26// 2. Document-based App scene27@main28struct TextEditorApp: App {29 var body: some Scene {30 DocumentGroup(newDocument: TextDocument()) { file in31 TextEditorView(document: file.$document)32 }33 .commands {34 TextFormattingCommands()35 }36 }37}38 39// 3. Editor view40struct TextEditorView: View {41 @Binding var document: TextDocument42 @State private var fontSize: CGFloat = 1443 @FocusState private var isEditorFocused: Bool44 45 var body: some View {46 TextEditor(text: $document.text)47 .font(.system(size: fontSize, design: .monospaced))48 .focused($isEditorFocused)49 .toolbar {50 ToolbarItem {51 Stepper("Font: \(Int(fontSize))pt",52 value: $fontSize, in: 8...72)53 }54 }55 .navigationTitle(document.text.prefix(30) + "...")56 .onAppear { isEditorFocused = true }57 }58}Document App Türleri
Tür | Kullanım | SwiftUI API |
|---|---|---|
**FileDocument** | Value-type document (struct) | `DocumentGroup(newDocument:)` |
**ReferenceFileDocument** | Class-based document (undo desteği) | `DocumentGroup(newDocument:) { }` |
**Custom UTType** | Kendi dosya formatın | `UTType` extension ile tanımla |
Drag & Drop Desteği
macOS uygulamalarında drag & drop kritik bir etkileşim pattern'idir:
swift
1struct FileDropView: View {2 @State private var droppedFiles: [URL] = []3 @State private var isTargeted = false4 5 var body: some View {6 VStack {7 ForEach(droppedFiles, id: \.self) { url in8 Label(url.lastPathComponent, systemImage: "doc")9 }10 }11 .frame(maxWidth: .infinity, maxHeight: .infinity)12 .background(isTargeted ? Color.blue.opacity(0.2) : Color.clear)13 .dropDestination(for: URL.self) { urls, _ in14 droppedFiles.append(contentsOf: urls)15 return true16 } isTargeted: { targeted in17 isTargeted = targeted18 }19 }20}Mac Catalyst: iOS'tan macOS'a {#catalyst}
Mevcut iPad uygulamanı Mac'e taşımanın en hızlı yolu Mac Catalyst:
swift
1// Catalyst-specific optimizasyonlar2#if targetEnvironment(macCatalyst)3extension AppDelegate {4 override func buildMenu(with builder: UIMenuBuilder) {5 // macOS menü çubuğuna özel komutlar ekle6 let prefsCommand = UIKeyCommand(7 title: "Tercihler...",8 action: #selector(showPreferences),9 input: ",",10 modifierFlags: .command11 )12 let prefsMenu = UIMenu(title: "", options: .displayInline, children: [prefsCommand])13 builder.insertChild(prefsMenu, atStartOfMenu: .application)14 }15}16#endifYaklaşım | Avantaj | Dezavantaj |
|---|---|---|
**Native SwiftUI** | Tam platform desteği | Ayrı geliştirme gerektirir |
**Mac Catalyst** | Hızlı geçiş, kod paylaşımı | AppKit erişimi kısıtlı |
**Designed for iPad** | Sıfır effort | macOS deneyimi zayıf |
AppKit Entegrasyonu {#appkit}
SwiftUI'da olmayan macOS feature'ları için AppKit kullanılır:
swift
1// NSViewRepresentable ile AppKit view'ı SwiftUI'da kullan2struct ColorWellView: NSViewRepresentable {3 @Binding var selectedColor: Color4 5 func makeNSView(context: Context) -> NSColorWell {6 let colorWell = NSColorWell()7 colorWell.action = #selector(Coordinator.colorChanged(_:))8 colorWell.target = context.coordinator9 return colorWell10 }11 12 func updateNSView(_ nsView: NSColorWell, context: Context) {13 nsView.color = NSColor(selectedColor)14 }15 16 func makeCoordinator() -> Coordinator {17 Coordinator(self)18 }19 20 class Coordinator: NSObject {21 var parent: ColorWellView22 23 init(_ parent: ColorWellView) { self.parent = parent }24 25 @objc func colorChanged(_ sender: NSColorWell) {26 parent.selectedColor = Color(nsColor: sender.color)27 }28 }29}Production Best Practices {#best-practices}
🔑 Çıkarımlar
- NavigationSplitView macOS'un temel navigation pattern'i
- Keyboard shortcuts zorunlu - Mac kullanıcıları klavyecidir
- Multi-window desteği düşün - özellikle document-based app'lerde
- Menu bar komutları anlamlı olsun
- Minimum pencere boyutu ayarla - küçülünce bozulmasın
- Dark/Light mode doğal destekle - macOS'ta çok önemli
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.

