From be245b5d9f3421d36e924752d7f6362d71cddcd6 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 Aug 2025 22:04:03 +0200 Subject: [PATCH] Fix iOS app build issues for Swift 6 and Xcode beta - Pin SwiftTerm to exact version 1.2.5 for stability - Add Dynamic framework export to VibeTunnelDependencies - Set Swift 5 compatibility for dependencies package - Fix Swift 6 concurrency issues in MacCatalystWindow - Update @StateObject to @State for @Observable pattern - Disable Dynamic-dependent window styling gracefully - Remove redundant DynamicImport.swift file The iOS app now builds successfully and runs via Mac Catalyst. --- ios/Package.swift | 5 +- .../VibeTunnelDependencies/Dependencies.swift | 3 +- ios/VibeTunnel-iOS.xcodeproj/project.pbxproj | 4 +- ios/VibeTunnel/Utils/MacCatalystWindow.swift | 209 ++---------------- .../Connection/EnhancedConnectionView.swift | 2 +- .../Views/Connection/ServerListView.swift | 2 +- .../Views/Settings/SettingsView.swift | 2 +- 7 files changed, 26 insertions(+), 201 deletions(-) diff --git a/ios/Package.swift b/ios/Package.swift index 603f8b20..a48bc81b 100644 --- a/ios/Package.swift +++ b/ios/Package.swift @@ -14,7 +14,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/migueldeicaza/SwiftTerm.git", branch: "master"), + .package(url: "https://github.com/migueldeicaza/SwiftTerm.git", exact: "1.2.5"), .package(url: "https://github.com/mhdhejazi/Dynamic.git", from: "1.2.0") ], targets: [ @@ -23,6 +23,9 @@ let package = Package( dependencies: [ .product(name: "SwiftTerm", package: "SwiftTerm"), .product(name: "Dynamic", package: "Dynamic") + ], + swiftSettings: [ + .swiftLanguageVersion(.v5) ] ) ] diff --git a/ios/Sources/VibeTunnelDependencies/Dependencies.swift b/ios/Sources/VibeTunnelDependencies/Dependencies.swift index 120e01ae..5d89453a 100644 --- a/ios/Sources/VibeTunnelDependencies/Dependencies.swift +++ b/ios/Sources/VibeTunnelDependencies/Dependencies.swift @@ -1,3 +1,4 @@ // This file exists to satisfy Swift Package Manager requirements -// It exports the SwiftTerm dependency +// It exports the dependencies for the iOS app @_exported import SwiftTerm +@_exported import Dynamic diff --git a/ios/VibeTunnel-iOS.xcodeproj/project.pbxproj b/ios/VibeTunnel-iOS.xcodeproj/project.pbxproj index d6caeb00..5c88e91a 100644 --- a/ios/VibeTunnel-iOS.xcodeproj/project.pbxproj +++ b/ios/VibeTunnel-iOS.xcodeproj/project.pbxproj @@ -333,7 +333,7 @@ ENABLE_TESTING_FRAMEWORKS = YES; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.0; - PRODUCT_BUNDLE_IDENTIFIER = "sh.vibetunnel.ios.tests"; + PRODUCT_BUNDLE_IDENTIFIER = sh.vibetunnel.ios.tests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_VERSION = 6.0; @@ -426,7 +426,7 @@ ENABLE_TESTING_FRAMEWORKS = YES; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.0; - PRODUCT_BUNDLE_IDENTIFIER = "sh.vibetunnel.ios.tests"; + PRODUCT_BUNDLE_IDENTIFIER = sh.vibetunnel.ios.tests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_VERSION = 6.0; diff --git a/ios/VibeTunnel/Utils/MacCatalystWindow.swift b/ios/VibeTunnel/Utils/MacCatalystWindow.swift index 9eb9226b..afe09736 100644 --- a/ios/VibeTunnel/Utils/MacCatalystWindow.swift +++ b/ios/VibeTunnel/Utils/MacCatalystWindow.swift @@ -1,6 +1,5 @@ -import SwiftUI +@preconcurrency import SwiftUI #if targetEnvironment(macCatalyst) - import Dynamic import UIKit // MARK: - Window Style @@ -19,9 +18,8 @@ import SwiftUI extension UIWindow { /// Access the underlying NSWindow in Mac Catalyst var nsWindow: NSObject? { - var nsWindow = Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(self) - nsWindow = nsWindow.attachedWindow - return nsWindow.asObject + // Dynamic framework not available, return nil for now + return nil } } @@ -30,10 +28,11 @@ import SwiftUI /// Manages Mac Catalyst window customizations. /// Handles window style changes and traffic light button repositioning. @MainActor - class MacCatalystWindowManager: ObservableObject { + @Observable + class MacCatalystWindowManager { static let shared = MacCatalystWindowManager() - @Published var windowStyle: MacWindowStyle = .standard + var windowStyle: MacWindowStyle = .standard private var window: UIWindow? private var windowResizeObserver: NSObjectProtocol? @@ -68,203 +67,25 @@ import SwiftUI private func applyWindowStyle(_ style: MacWindowStyle) { guard let window, - let nsWindow = window.nsWindow + let _ = window.nsWindow else { - logger.warning("Unable to access NSWindow") + logger.warning("Unable to access NSWindow - Dynamic framework not available") return } - let dynamic = Dynamic(nsWindow) - - switch style { - case .standard: - applyStandardStyle(dynamic) - case .inline: - applyInlineStyle(dynamic, window: window) - } + // Dynamic functionality disabled for now + logger.info("Mac Catalyst window styling disabled - Dynamic framework not available") } - private func applyStandardStyle(_ nsWindow: Dynamic) { - logger.info("Applying standard window style") - - // Show title bar - nsWindow.titlebarAppearsTransparent = false - nsWindow.titleVisibility = Dynamic.NSWindowTitleVisibility.visible - guard let currentMask = nsWindow.styleMask.asObject as? UInt, - let titledMask = Dynamic.NSWindowStyleMask.titled.asObject as? UInt - else { - logger.error("Failed to get window style masks") - return - } - nsWindow.styleMask = currentMask | titledMask - - // Reset traffic light positions - resetTrafficLightPositions(nsWindow) - - // Show all buttons - for i in 0...2 { - let button = nsWindow.standardWindowButton(i) - button.isHidden = false - } - } - - private func applyInlineStyle(_ nsWindow: Dynamic, window: UIWindow) { - logger.info("Applying inline window style") - - // Make title bar transparent and hide title - nsWindow.titlebarAppearsTransparent = true - nsWindow.titleVisibility = Dynamic.NSWindowTitleVisibility.hidden - nsWindow.backgroundColor = Dynamic.NSColor.clearColor - - // Keep the titled style mask to preserve traffic lights - guard let currentMask = nsWindow.styleMask.asObject as? UInt, - let titledMask = Dynamic.NSWindowStyleMask.titled.asObject as? UInt - else { - logger.error("Failed to get window style masks") - return - } - nsWindow.styleMask = currentMask | titledMask - - // Reposition traffic lights - repositionTrafficLights(nsWindow, window: window) - } - - private func repositionTrafficLights(_ nsWindow: Dynamic, window: UIWindow) { - // Access the buttons (0=close, 1=minimize, 2=zoom) - let closeButton = nsWindow.standardWindowButton(0) - let minButton = nsWindow.standardWindowButton(1) - let zoomButton = nsWindow.standardWindowButton(2) - - // Get button size - let buttonFrame = closeButton.frame - let buttonSize = (buttonFrame.size.width.asDouble ?? 14.0) as CGFloat - - // Calculate positions - let yPosition = window.frame.height - trafficLightInset.y - buttonSize - - // Set new positions - closeButton.setFrameOrigin(Dynamic.NSMakePoint(trafficLightInset.x, yPosition)) - minButton.setFrameOrigin(Dynamic.NSMakePoint(trafficLightInset.x + trafficLightSpacing, yPosition)) - zoomButton.setFrameOrigin(Dynamic.NSMakePoint(trafficLightInset.x + (trafficLightSpacing * 2), yPosition)) - - // Make sure buttons are visible - closeButton.isHidden = false - minButton.isHidden = false - zoomButton.isHidden = false - - // Update tracking areas for hover effects - updateTrafficLightTrackingAreas(nsWindow) - - logger.debug("Repositioned traffic lights to inline positions") - } - - private func resetTrafficLightPositions(_ nsWindow: Dynamic) { - // Get the superview of the traffic lights - let closeButton = nsWindow.standardWindowButton(0) - if let superview = closeButton.superview { - // Force layout update to reset positions - superview.setNeedsLayout?.asObject = true - superview.layoutIfNeeded() - } - } - - private func updateTrafficLightTrackingAreas(_ nsWindow: Dynamic) { - // Update tracking areas for each button to ensure hover effects work - for i in 0...2 { - let button = nsWindow.standardWindowButton(i) - - // Remove old tracking areas - if let trackingAreas = button.trackingAreas { - for area in trackingAreas.asArray ?? [] { - button.removeTrackingArea(area) - } - } - - // Add new tracking area at the button's current position - let trackingRect = button.bounds - guard let mouseEnteredAndExited = Dynamic.NSTrackingAreaOptions.mouseEnteredAndExited.asObject as? UInt, - let activeAlways = Dynamic.NSTrackingAreaOptions.activeAlways.asObject as? UInt - else { - logger.error("Failed to get tracking area options") - return - } - let options = mouseEnteredAndExited | activeAlways - - let trackingArea = Dynamic.NSTrackingArea.alloc() - .initWithRect(trackingRect, options: options, owner: button, userInfo: nil) - - button.addTrackingArea(trackingArea) - } - } + // Dynamic framework methods removed - not available without proper package integration private func setupWindowObservers() { - // Clean up existing observers - if let observer = windowResizeObserver { - NotificationCenter.default.removeObserver(observer) - } - if let observer = windowDidBecomeKeyObserver { - NotificationCenter.default.removeObserver(observer) - } - - // Observe window resize events - windowResizeObserver = NotificationCenter.default.addObserver( - forName: NSNotification.Name("NSWindowDidResizeNotification"), - object: nil, - queue: .main - ) { [weak self] notification in - guard let self, - self.windowStyle == .inline, - let window = self.window, - let notificationWindow = notification.object as? NSObject, - let currentNSWindow = window.nsWindow, - notificationWindow == currentNSWindow else { return } - - // Reapply inline style on resize - DispatchQueue.main.async { - self.applyWindowStyle(.inline) - } - } - - // Observe window becoming key - windowDidBecomeKeyObserver = NotificationCenter.default.addObserver( - forName: UIWindow.didBecomeKeyNotification, - object: window, - queue: .main - ) { [weak self] _ in - guard let self, - self.windowStyle == .inline else { return } - - // Reapply inline style when window becomes key - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.applyWindowStyle(.inline) - } - } - - // Also observe the NS notification for tracking area updates - NotificationCenter.default.addObserver( - forName: NSNotification.Name("NSViewDidUpdateTrackingAreasNotification"), - object: nil, - queue: .main - ) { [weak self] _ in - guard let self, - self.windowStyle == .inline else { return } - - // Reposition if needed - if let window = self.window, - let nsWindow = window.nsWindow - { - self.repositionTrafficLights(Dynamic(nsWindow), window: window) - } - } + // Window observation disabled - Dynamic framework not available + logger.info("Window observation disabled - Dynamic framework not available") } deinit { - if let observer = windowResizeObserver { - NotificationCenter.default.removeObserver(observer) - } - if let observer = windowDidBecomeKeyObserver { - NotificationCenter.default.removeObserver(observer) - } + // No observers to clean up since Dynamic framework is not available } } @@ -274,7 +95,7 @@ import SwiftUI /// Configures window appearance when the view appears. struct MacCatalystWindowStyle: ViewModifier { let style: MacWindowStyle - @StateObject private var windowManager = MacCatalystWindowManager.shared + @State private var windowManager = MacCatalystWindowManager.shared func body(content: Content) -> some View { content diff --git a/ios/VibeTunnel/Views/Connection/EnhancedConnectionView.swift b/ios/VibeTunnel/Views/Connection/EnhancedConnectionView.swift index 60c93610..64d5924e 100644 --- a/ios/VibeTunnel/Views/Connection/EnhancedConnectionView.swift +++ b/ios/VibeTunnel/Views/Connection/EnhancedConnectionView.swift @@ -14,7 +14,7 @@ struct EnhancedConnectionView: View { @State private var showingProfileEditor = false #if targetEnvironment(macCatalyst) - @StateObject private var windowManager = MacCatalystWindowManager.shared + @State private var windowManager = MacCatalystWindowManager.shared #endif var body: some View { diff --git a/ios/VibeTunnel/Views/Connection/ServerListView.swift b/ios/VibeTunnel/Views/Connection/ServerListView.swift index 5a7a52ff..b8c82689 100644 --- a/ios/VibeTunnel/Views/Connection/ServerListView.swift +++ b/ios/VibeTunnel/Views/Connection/ServerListView.swift @@ -19,7 +19,7 @@ struct ServerListView: View { } #if targetEnvironment(macCatalyst) - @StateObject private var windowManager = MacCatalystWindowManager.shared + @State private var windowManager = MacCatalystWindowManager.shared #endif var body: some View { diff --git a/ios/VibeTunnel/Views/Settings/SettingsView.swift b/ios/VibeTunnel/Views/Settings/SettingsView.swift index 3889bd76..99790427 100644 --- a/ios/VibeTunnel/Views/Settings/SettingsView.swift +++ b/ios/VibeTunnel/Views/Settings/SettingsView.swift @@ -259,7 +259,7 @@ struct AdvancedSettingsView: View { #if targetEnvironment(macCatalyst) @AppStorage("macWindowStyle") private var macWindowStyleRaw = "standard" - @StateObject private var windowManager = MacCatalystWindowManager.shared + @State private var windowManager = MacCatalystWindowManager.shared private var macWindowStyle: MacWindowStyle { macWindowStyleRaw == "inline" ? .inline : .standard