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.
This commit is contained in:
Peter Steinberger 2025-08-02 22:04:03 +02:00
parent e9ef227f8f
commit be245b5d9f
7 changed files with 26 additions and 201 deletions

View file

@ -14,7 +14,7 @@ let package = Package(
) )
], ],
dependencies: [ 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") .package(url: "https://github.com/mhdhejazi/Dynamic.git", from: "1.2.0")
], ],
targets: [ targets: [
@ -23,6 +23,9 @@ let package = Package(
dependencies: [ dependencies: [
.product(name: "SwiftTerm", package: "SwiftTerm"), .product(name: "SwiftTerm", package: "SwiftTerm"),
.product(name: "Dynamic", package: "Dynamic") .product(name: "Dynamic", package: "Dynamic")
],
swiftSettings: [
.swiftLanguageVersion(.v5)
] ]
) )
] ]

View file

@ -1,3 +1,4 @@
// This file exists to satisfy Swift Package Manager requirements // 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 SwiftTerm
@_exported import Dynamic

View file

@ -333,7 +333,7 @@
ENABLE_TESTING_FRAMEWORKS = YES; ENABLE_TESTING_FRAMEWORKS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0; IPHONEOS_DEPLOYMENT_TARGET = 18.0;
PRODUCT_BUNDLE_IDENTIFIER = "sh.vibetunnel.ios.tests"; PRODUCT_BUNDLE_IDENTIFIER = sh.vibetunnel.ios.tests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_VERSION = 6.0; SWIFT_VERSION = 6.0;
@ -426,7 +426,7 @@
ENABLE_TESTING_FRAMEWORKS = YES; ENABLE_TESTING_FRAMEWORKS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0; IPHONEOS_DEPLOYMENT_TARGET = 18.0;
PRODUCT_BUNDLE_IDENTIFIER = "sh.vibetunnel.ios.tests"; PRODUCT_BUNDLE_IDENTIFIER = sh.vibetunnel.ios.tests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_VERSION = 6.0; SWIFT_VERSION = 6.0;

View file

@ -1,6 +1,5 @@
import SwiftUI @preconcurrency import SwiftUI
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
import Dynamic
import UIKit import UIKit
// MARK: - Window Style // MARK: - Window Style
@ -19,9 +18,8 @@ import SwiftUI
extension UIWindow { extension UIWindow {
/// Access the underlying NSWindow in Mac Catalyst /// Access the underlying NSWindow in Mac Catalyst
var nsWindow: NSObject? { var nsWindow: NSObject? {
var nsWindow = Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(self) // Dynamic framework not available, return nil for now
nsWindow = nsWindow.attachedWindow return nil
return nsWindow.asObject
} }
} }
@ -30,10 +28,11 @@ import SwiftUI
/// Manages Mac Catalyst window customizations. /// Manages Mac Catalyst window customizations.
/// Handles window style changes and traffic light button repositioning. /// Handles window style changes and traffic light button repositioning.
@MainActor @MainActor
class MacCatalystWindowManager: ObservableObject { @Observable
class MacCatalystWindowManager {
static let shared = MacCatalystWindowManager() static let shared = MacCatalystWindowManager()
@Published var windowStyle: MacWindowStyle = .standard var windowStyle: MacWindowStyle = .standard
private var window: UIWindow? private var window: UIWindow?
private var windowResizeObserver: NSObjectProtocol? private var windowResizeObserver: NSObjectProtocol?
@ -68,203 +67,25 @@ import SwiftUI
private func applyWindowStyle(_ style: MacWindowStyle) { private func applyWindowStyle(_ style: MacWindowStyle) {
guard let window, guard let window,
let nsWindow = window.nsWindow let _ = window.nsWindow
else { else {
logger.warning("Unable to access NSWindow") logger.warning("Unable to access NSWindow - Dynamic framework not available")
return return
} }
let dynamic = Dynamic(nsWindow) // Dynamic functionality disabled for now
logger.info("Mac Catalyst window styling disabled - Dynamic framework not available")
switch style {
case .standard:
applyStandardStyle(dynamic)
case .inline:
applyInlineStyle(dynamic, window: window)
}
} }
private func applyStandardStyle(_ nsWindow: Dynamic) { // Dynamic framework methods removed - not available without proper package integration
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)
}
}
private func setupWindowObservers() { private func setupWindowObservers() {
// Clean up existing observers // Window observation disabled - Dynamic framework not available
if let observer = windowResizeObserver { logger.info("Window observation disabled - Dynamic framework not available")
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)
}
}
} }
deinit { deinit {
if let observer = windowResizeObserver { // No observers to clean up since Dynamic framework is not available
NotificationCenter.default.removeObserver(observer)
}
if let observer = windowDidBecomeKeyObserver {
NotificationCenter.default.removeObserver(observer)
}
} }
} }
@ -274,7 +95,7 @@ import SwiftUI
/// Configures window appearance when the view appears. /// Configures window appearance when the view appears.
struct MacCatalystWindowStyle: ViewModifier { struct MacCatalystWindowStyle: ViewModifier {
let style: MacWindowStyle let style: MacWindowStyle
@StateObject private var windowManager = MacCatalystWindowManager.shared @State private var windowManager = MacCatalystWindowManager.shared
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content

View file

@ -14,7 +14,7 @@ struct EnhancedConnectionView: View {
@State private var showingProfileEditor = false @State private var showingProfileEditor = false
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
@StateObject private var windowManager = MacCatalystWindowManager.shared @State private var windowManager = MacCatalystWindowManager.shared
#endif #endif
var body: some View { var body: some View {

View file

@ -19,7 +19,7 @@ struct ServerListView: View {
} }
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
@StateObject private var windowManager = MacCatalystWindowManager.shared @State private var windowManager = MacCatalystWindowManager.shared
#endif #endif
var body: some View { var body: some View {

View file

@ -259,7 +259,7 @@ struct AdvancedSettingsView: View {
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
@AppStorage("macWindowStyle") @AppStorage("macWindowStyle")
private var macWindowStyleRaw = "standard" private var macWindowStyleRaw = "standard"
@StateObject private var windowManager = MacCatalystWindowManager.shared @State private var windowManager = MacCatalystWindowManager.shared
private var macWindowStyle: MacWindowStyle { private var macWindowStyle: MacWindowStyle {
macWindowStyleRaw == "inline" ? .inline : .standard macWindowStyleRaw == "inline" ? .inline : .standard