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: [
.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)
]
)
]

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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 {

View file

@ -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 {

View file

@ -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