From 913cd1b95bab68cbed0715c3ff4351862575b31f Mon Sep 17 00:00:00 2001 From: Shin Yamamoto Date: Wed, 5 Nov 2025 21:45:20 +0900 Subject: [PATCH 1/3] Support dynamic content updates in SwiftUI Store content hosting controller reference and update it dynamically in updateUIViewController(), allowing panel content to change without recreation. Resolved #672 --- .../SamplesSwiftUI/UseCases/MainView.swift | 48 ++++++++++++++----- Sources/SwiftUI/FloatingPanelView.swift | 20 ++++++++ 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift b/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift index faedc8ac..e993db22 100644 --- a/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift +++ b/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift @@ -6,26 +6,28 @@ import UIKit import os.log struct MainView: View { + enum CardContent: String, CaseIterable, Identifiable { + case list + case detail + + var id: String { rawValue } + } @State private var panelLayout: FloatingPanelLayout? = MyFloatingPanelLayout() @State private var panelState: FloatingPanelState? + @State private var selectedContent: CardContent = .list var body: some View { ZStack { Color.orange .ignoresSafeArea() - .floatingPanel( - coordinator: MyPanelCoordinator.self - ) { proxy in - ContentView(proxy: proxy) - } - .floatingPanelSurfaceAppearance(.transparent()) - .floatingPanelLayout(panelLayout) - .floatingPanelState($panelState) - .onChange(of: panelState) { newValue in - Logger().debug("Panel state changed: \(newValue ?? .hidden)") - } - VStack(spacing: 32) { + Picker("type", selection: $selectedContent) { + ForEach(CardContent.allCases) { + type in + Text(type.rawValue).tag(type) + } + } + .pickerStyle(.segmented) Button("Move to full") { withAnimation(.interactiveSpring) { panelState = .full @@ -46,8 +48,29 @@ struct MainView: View { Text("Switch to My layout") } } + Spacer() + } + } + .floatingPanel( + coordinator: MyPanelCoordinator.self + ) { proxy in + switch selectedContent { + case .list: + ContentView(proxy: proxy) + case .detail: + VStack { + Text("off") + .padding(.top, 32) + Spacer() + } } } + .floatingPanelSurfaceAppearance(.transparent()) + .floatingPanelLayout(panelLayout) + .floatingPanelState($panelState) + .onChange(of: panelState) { newValue in + Logger().debug("Panel state changed: \(newValue ?? .hidden)") + } } } @@ -104,3 +127,4 @@ class MyFloatingPanelLayout: FloatingPanelLayout { #Preview("MainView") { MainView() } + diff --git a/Sources/SwiftUI/FloatingPanelView.swift b/Sources/SwiftUI/FloatingPanelView.swift index d7ba4d11..b11e2aeb 100644 --- a/Sources/SwiftUI/FloatingPanelView.swift +++ b/Sources/SwiftUI/FloatingPanelView.swift @@ -110,7 +110,11 @@ struct FloatingPanelView: UIViewControllerRep _ uiViewController: UIHostingController, context: Context ) { + uiViewController.rootView = main + + context.coordinator.updateContent(content(context.coordinator.proxy)) context.coordinator.onUpdate(context: context) + applyEnvironment(context: context) applyAnimatableEnvironment(context: context) } @@ -160,6 +164,9 @@ class FloatingPanelCoordinatorProxy { private var subscriptions: Set = Set() + // Store a reference to the content hosting controller for dynamic updates + private weak var contentHostingController: UIViewController? + var proxy: FloatingPanelProxy { origin.proxy } var controller: FloatingPanelController { origin.controller } @@ -181,12 +188,25 @@ class FloatingPanelCoordinatorProxy { mainHostingController: UIHostingController
, contentHostingController: UIHostingController ) { + // Store the content hosting controller reference + self.contentHostingController = contentHostingController + origin.setupFloatingPanel( mainHostingController: mainHostingController, contentHostingController: contentHostingController ) } + /// Updates the content of the floating panel with new content. + func updateContent(_ newContent: Content) { + guard + let hostingController = contentHostingController as? UIHostingController + else { + return + } + hostingController.rootView = newContent + } + func onUpdate( context: UIViewControllerRepresentableContext ) where Representable: UIViewControllerRepresentable { From 2ba4badf54ce10548082e7aba6dce7ddedb173ea Mon Sep 17 00:00:00 2001 From: Shin Yamamoto Date: Sun, 23 Nov 2025 20:28:00 +0900 Subject: [PATCH 2/3] Improve SamplesSwiftUI --- .../SamplesSwiftUI/UseCases/MainView.swift | 15 ++++++++---- .../SamplesSwiftUI/Views/BackgroundView.swift | 23 +++++++++++++++++++ .../SamplesSwiftUI/Views/ContentView.swift | 9 +++----- 3 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 Examples/SamplesSwiftUI/SamplesSwiftUI/Views/BackgroundView.swift diff --git a/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift b/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift index e993db22..35412bb3 100644 --- a/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift +++ b/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift @@ -58,11 +58,19 @@ struct MainView: View { case .list: ContentView(proxy: proxy) case .detail: - VStack { - Text("off") - .padding(.top, 32) + HStack { + Spacer() + VStack { + Text("Detail content") + .padding(.top, 32) + Spacer() + } Spacer() } + .padding() + .background { + BackgroundView() + } } } .floatingPanelSurfaceAppearance(.transparent()) @@ -127,4 +135,3 @@ class MyFloatingPanelLayout: FloatingPanelLayout { #Preview("MainView") { MainView() } - diff --git a/Examples/SamplesSwiftUI/SamplesSwiftUI/Views/BackgroundView.swift b/Examples/SamplesSwiftUI/SamplesSwiftUI/Views/BackgroundView.swift new file mode 100644 index 00000000..0f9968eb --- /dev/null +++ b/Examples/SamplesSwiftUI/SamplesSwiftUI/Views/BackgroundView.swift @@ -0,0 +1,23 @@ +import SwiftUI + +struct BackgroundView: View { + var body: some View { + GeometryReader { geometry in + Rectangle() + .fill(.clear) + .frame(height: geometry.size.height * 2) + .backgroundEffect() + } + } +} + +extension View { + @ViewBuilder + fileprivate func backgroundEffect() -> some View { + if #available(iOS 26, *) { + self.glassEffect(.regular, in: .rect) + } else { + self.background(.regularMaterial) + } + } +} diff --git a/Examples/SamplesSwiftUI/SamplesSwiftUI/Views/ContentView.swift b/Examples/SamplesSwiftUI/SamplesSwiftUI/Views/ContentView.swift index d7d01e96..8db4c47c 100644 --- a/Examples/SamplesSwiftUI/SamplesSwiftUI/Views/ContentView.swift +++ b/Examples/SamplesSwiftUI/SamplesSwiftUI/Views/ContentView.swift @@ -15,6 +15,7 @@ struct ContentView: View { .frame(maxWidth: .infinity, alignment: .leading) .frame(height: 60) .background(.clear) + .padding(.horizontal) } } } @@ -28,6 +29,7 @@ struct ContentView: View { .frame(maxWidth: .infinity, alignment: .leading) .frame(height: 60) .background(.clear) + .padding(.horizontal) } } } @@ -38,12 +40,7 @@ struct ContentView: View { } // Prevent revealing underlying content at the bottom of the panel when the panel is moving beyond its fully‑expanded position. .background { - GeometryReader { geometry in - Rectangle() - .fill(.clear) - .frame(height: geometry.size.height * 2) - .background(.regularMaterial) - } + BackgroundView() } } } From 7cd414945bbe53f1ec22cdf246bee637e31077ab Mon Sep 17 00:00:00 2001 From: Shin Yamamoto Date: Thu, 27 Nov 2025 16:32:23 +0900 Subject: [PATCH 3/3] ci: use Xcode 26.0.1 for all builds --- .github/workflows/ci.yml | 74 ++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 476c69ab..e73ca60e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,13 @@ name: ci -on: +on: push: - branches: + branches: - master pull_request: - branches: - - '*' - workflow_dispatch: + branches: + - "*" + workflow_dispatch: jobs: build: @@ -40,33 +40,33 @@ jobs: - os: "26.0.1" xcode: "26.0.1" sim: "iPhone 16 Pro" - parallel: NO # Stop random test job failures + parallel: NO # Stop random test job failures runs-on: macos-15 - os: "18.5" xcode: "16.4" sim: "iPhone 16 Pro" - parallel: NO # Stop random test job failures + parallel: NO # Stop random test job failures runs-on: macos-15 - os: "17.5" xcode: "15.4" sim: "iPhone 15 Pro" - parallel: NO # Stop random test job failures + parallel: NO # Stop random test job failures runs-on: macos-14 steps: - - uses: actions/checkout@v4 - - name: Testing in iOS ${{ matrix.os }} - run: | - xcodebuild clean test \ - -workspace FloatingPanel.xcworkspace \ - -scheme FloatingPanel \ - -destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' \ - -parallel-testing-enabled '${{ matrix.parallel }}' + - uses: actions/checkout@v4 + - name: Testing in iOS ${{ matrix.os }} + run: | + xcodebuild clean test \ + -workspace FloatingPanel.xcworkspace \ + -scheme FloatingPanel \ + -destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' \ + -parallel-testing-enabled '${{ matrix.parallel }}' timeout-minutes: 20 example: runs-on: macos-15 env: - DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_26.0.1.app/Contents/Developer strategy: fail-fast: false matrix: @@ -78,13 +78,13 @@ jobs: - example: "SamplesObjC" - example: "SamplesSwiftUI" steps: - - uses: actions/checkout@v4 - - name: Building ${{ matrix.example }} - run: | - xcodebuild clean build \ - -workspace FloatingPanel.xcworkspace \ - -scheme ${{ matrix.example }} \ - -sdk iphonesimulator + - uses: actions/checkout@v4 + - name: Building ${{ matrix.example }} + run: | + xcodebuild clean build \ + -workspace FloatingPanel.xcworkspace \ + -scheme ${{ matrix.example }} \ + -sdk iphonesimulator swiftpm: runs-on: ${{ matrix.runs-on }} @@ -93,7 +93,7 @@ jobs: strategy: fail-fast: false matrix: - xcode: ["16.4", "15.4"] + xcode: ["26.0.1", "16.4", "15.4"] platform: [iphoneos, iphonesimulator] arch: [x86_64, arm64] exclude: @@ -119,20 +119,20 @@ jobs: sys: "ios17.2-simulator" runs-on: macos-14 steps: - - uses: actions/checkout@v4 - - name: "Swift Package Manager build" - run: | - xcrun swift build \ - --sdk "$(xcrun --sdk ${{ matrix.platform }} --show-sdk-path)" \ - -Xswiftc "-target" -Xswiftc "${{ matrix.arch }}-apple-${{ matrix.sys }}" + - uses: actions/checkout@v4 + - name: "Swift Package Manager build" + run: | + xcrun swift build \ + --sdk "$(xcrun --sdk ${{ matrix.platform }} --show-sdk-path)" \ + -Xswiftc "-target" -Xswiftc "${{ matrix.arch }}-apple-${{ matrix.sys }}" cocoapods: runs-on: macos-15 env: - DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_26.0.1.app/Contents/Developer steps: - - uses: actions/checkout@v4 - - name: "CocoaPods: pod lib lint" - run: pod lib lint --allow-warnings --verbose - - name: "CocoaPods: pod spec lint" - run: pod spec lint --allow-warnings --verbose + - uses: actions/checkout@v4 + - name: "CocoaPods: pod lib lint" + run: pod lib lint --allow-warnings --verbose + - name: "CocoaPods: pod spec lint" + run: pod spec lint --allow-warnings --verbose